feat: 代码标准化和文件重命名

## 新增功能
- 创建统一的公共函数库 (common/logging.sh, common/error_handler.sh)
- 添加功能演示脚本 (common/demo_usage.sh)
- 完善的使用文档 (common/README.md)

## 代码重构
- 重构所有脚本使用统一的公共库
- 为所有函数添加完整的文档注释
- 统一代码格式(4空格缩进、严格模式)
- 标准化错误处理和日志输出

## 文件重命名
- gcp/create_ai_project.sh → gcp/create_ai_projects.sh (单复数统一)
- gcp/delete_all_project.sh → gcp/delete_all_projects.sh (单复数统一)
- linux/install_ohmyzsh.sh → linux/install_oh_my_zsh.sh (专有名词规范)
- linux/create_raid0_with_ext4.sh → linux/create_raid0_array.sh (简化命名)
- common/example.sh → common/demo_usage.sh (更具描述性)

## 技术改进
- 使用 readonly 声明常量
- 启用 set -euo pipefail 严格模式
- 统一的 ANSI 颜色日志输出
- 完善的命令重试机制
- 栈追踪支持
This commit is contained in:
2025-12-26 14:47:18 +08:00
commit 7def817482
11 changed files with 2312 additions and 0 deletions

389
oci/create_instance.sh Normal file
View File

@@ -0,0 +1,389 @@
#!/bin/bash
# ============================================================================
# 文件名: create_instance.sh
# 描述: Oracle Cloud Infrastructure 虚拟机批量部署工具
# 作者: Cloud Tools Project
# 版本: 2.0.0
# ============================================================================
set -euo pipefail
# 获取脚本目录
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
# 加载公共库
source "${PROJECT_ROOT}/common/logging.sh"
source "${PROJECT_ROOT}/common/error_handler.sh"
# 默认配置参数
readonly DEFAULT_NUMBER=1
readonly DEFAULT_SHAPE="VM.Standard.A1.Flex"
readonly DEFAULT_SHAPE_CONFIG="1+6"
readonly DEFAULT_IMAGE_NAME="Canonical-Ubuntu-20.04-aarch64-2025.05.20-0"
readonly DEFAULT_BOOT_VOLUME_SIZE=0
readonly DEFAULT_VPU=120
readonly DEFAULT_DOMAIN=0
#
# 检查必要的依赖工具是否安装
#
check_dependencies() {
local deps=("oci" "jq" "base64" "mkfifo")
local missing=()
for cmd in "${deps[@]}"; do
if ! check_command "$cmd"; then
missing+=("$cmd")
fi
done
if [[ ${#missing[@]} -gt 0 ]]; then
log_error "缺少必要依赖: ${missing[*]}"
exit 1
fi
log_success "运行环境检查通过"
}
#
# 生成随机安全密码
#
# 返回:
# 16位随机密码
#
generate_password() {
LC_ALL=C tr -dc 'A-Za-z0-9!#%&()*+,-.:;<=>?@[]^_~' </dev/urandom | head -c 16
}
#
# 生成实例初始化的 cloud-init 用户数据
#
# 参数:
# $1 - root 密码
#
# 返回:
# Base64 编码的 user_data
#
generate_user_data() {
local password="$1"
cat <<EOF | base64 -w 0
#!/bin/bash
echo root:"$password" | chpasswd
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config
rm -f /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
systemctl restart sshd
EOF
}
#
# 生成实例规格配置
#
# 参数:
# $1 - 规格配置字符串 (格式: CPU+内存 或 CPU+内存+基线)
#
# 返回:
# JSON 格式的规格配置
#
generate_shape_config() {
local config_str="$1"
IFS='+' read -ra parts <<< "$config_str"
case "${#parts[@]}" in
2)
echo "{\"ocpus\":${parts[0]},\"memoryInGBs\":${parts[1]}}"
;;
3)
case "${parts[2]}" in
0.125)
echo "{\"ocpus\":${parts[0]},\"memoryInGBs\":${parts[1]},\"baselineOcpuUtilization\":\"BASELINE_1_8\"}"
;;
0.5)
echo "{\"ocpus\":${parts[0]},\"memoryInGBs\":${parts[1]},\"baselineOcpuUtilization\":\"BASELINE_1_2\"}"
;;
*)
log_error "无效的 baseline 配置: ${parts[2]}"
exit 1
;;
esac
;;
*)
log_error "无效的 shape 配置格式: $config_str"
exit 1
;;
esac
}
#
# 配置 OCI 网络资源
#
# 参数:
# $1 - Compartment OCID
# $2 - FIFO 文件路径用于返回子网ID
#
configure_network() {
local ocid="$1"
local fifo="$2"
local vcn_dns="vcn$(shuf -i 100000-999999 -n 1)"
local subnet_dns="subnet$(shuf -i 100000-999999 -n 1)"
# 创建 VCN
log_info "正在创建虚拟网络 (DNS: $vcn_dns)"
local vcn_id
vcn_id=$(oci network vcn create \
--compartment-id "$ocid" \
--cidr-blocks '["10.0.0.0/16"]' \
--is-ipv6-enabled true \
--dns-label "$vcn_dns" \
--query 'data.id' \
--raw-output 2>/dev/null)
check_not_empty "$vcn_id" "vcn_id"
log_success "VCN 创建成功 (ID: $vcn_id)"
# 生成 IPv6 CIDR
log_info "正在生成 IPv6 CIDR..."
local ipv6_cidr
ipv6_cidr=$(oci network vcn get --vcn-id "$vcn_id" --query 'data."ipv6-cidr-blocks"[0]' --raw-output)
ipv6_cidr="${ipv6_cidr%00::*}$((RANDOM%90+10))::/64"
log_success "IPv6 CIDR 生成成功: $ipv6_cidr"
# 创建子网
log_info "正在创建子网 (DNS: $subnet_dns)"
local subnet_id
subnet_id=$(OCI_CLI_SUPPRESS_JSON_OUTPUT=true oci network subnet create \
--compartment-id "$ocid" \
--vcn-id "$vcn_id" \
--cidr-block "10.0.0.0/16" \
--ipv6-cidr-block "$ipv6_cidr" \
--dns-label "$subnet_dns" \
--query 'data.id' \
--raw-output 2>/dev/null)
check_not_empty "$subnet_id" "subnet_id"
log_success "子网创建成功 (ID: $subnet_id)"
# 配置网关
log_info "正在配置网络网关..."
local gateway_id
gateway_id=$(oci network internet-gateway create \
--compartment-id "$ocid" \
--vcn-id "$vcn_id" \
--is-enabled true \
--query 'data.id' \
--raw-output 2>/dev/null)
check_return $? "网关创建失败"
log_success "网关创建成功 (ID: $gateway_id)"
# 配置路由表
log_info "正在配置路由表..."
local rt_id
rt_id=$(oci network route-table list \
--compartment-id "$ocid" \
--vcn-id "$vcn_id" \
--query 'data[0].id' \
--raw-output 2>/dev/null)
check_not_empty "$rt_id" "rt_id"
oci network route-table update --rt-id "$rt_id" \
--route-rules "[
{\"cidrBlock\":\"0.0.0.0/0\",\"networkEntityId\":\"$gateway_id\"},
{\"cidrBlock\":\"0:0:0:0:0:0:0:0/0\",\"networkEntityId\":\"$gateway_id\"}
]" --force >/dev/null
check_return $? "路由配置失败"
log_success "路由表配置成功"
# 配置安全组
log_info "正在配置安全组规则..."
local sg_id
sg_id=$(oci network security-list list \
--compartment-id "$ocid" \
--vcn-id "$vcn_id" \
--query 'data[0].id' \
--raw-output 2>/dev/null)
oci network security-list update --security-list-id "$sg_id" \
--ingress-security-rules '[]' --force >/dev/null
check_return $? "安全组清空失败"
oci network security-list update --security-list-id "$sg_id" \
--ingress-security-rules "[
{\"protocol\":\"all\",\"source\":\"0.0.0.0/0\",\"sourceType\":\"CIDR_BLOCK\"},
{\"protocol\":\"all\",\"source\":\"::/0\",\"sourceType\":\"CIDR_BLOCK\"}
]" --force >/dev/null
check_return $? "安全组规则更新失败"
log_success "安全组规则配置成功"
# 返回子网ID通过 FIFO
echo "$subnet_id" > "$fifo"
}
#
# 显示帮助信息
#
show_help() {
cat <<EOF
用法: ${0##*/} [选项]
选项:
-n, --number <数量> 创建实例数量 (默认: $DEFAULT_NUMBER)
--ocid <OCID> 区域ID (自动获取)
--shape <形状> 实例规格 (默认: $DEFAULT_SHAPE)
--shape_config <配置> 资源配置格式: CPU+内存[+基线] (默认: $DEFAULT_SHAPE_CONFIG)
--subnet_id <子网ID> 指定现有子网ID (自动创建)
--image_name <镜像名> 系统镜像名称 (默认: $DEFAULT_IMAGE_NAME)
--vpu <数值> 启动卷性能单位 (默认: $DEFAULT_VPU)
--boot_volume_size <GB> 启动卷大小 (默认: 自动)
--domain <索引> 可用域序号 (默认: $DEFAULT_DOMAIN)
--password <密码> 指定 root 密码 (默认自动生成)
-h, --help 显示本帮助信息
示例:
$0 -n 3 --shape VM.Standard.E4.Flex --shape_config 2+16
EOF
}
#
# 主函数 - 批量创建 OCI 实例
#
main() {
log_info "============ OCI 实例批量部署工具 ============"
# 参数初始化
local number=$DEFAULT_NUMBER
local shape=$DEFAULT_SHAPE
local shape_config=$DEFAULT_SHAPE_CONFIG
local image_name=$DEFAULT_IMAGE_NAME
local boot_size=$DEFAULT_BOOT_VOLUME_SIZE
local vpu=$DEFAULT_VPU
local domain=$DEFAULT_DOMAIN
local ocid password subnet_id
# 参数解析
while [[ $# -gt 0 ]]; do
case "$1" in
-n|--number) number="$2"; shift 2 ;;
--ocid) ocid="$2"; shift 2 ;;
--shape) shape="$2"; shift 2 ;;
--shape_config) shape_config="$2"; shift 2 ;;
--subnet_id) subnet_id="$2"; shift 2 ;;
--image_name) image_name="$2"; shift 2 ;;
--vpu) vpu="$2"; shift 2 ;;
--boot_volume_size) boot_size="$2"; shift 2 ;;
--domain) domain="$2"; shift 2 ;;
--password) password="$2"; shift 2 ;;
-h|--help) show_help; exit 0 ;;
*) log_error "无效参数: $1"; exit 1 ;;
esac
done
# 环境检查
check_dependencies
# 自动生成密码
if [[ -z "${password:-}" ]]; then
password=$(generate_password)
fi
log_warning "使用密码: $password"
# OCID 自动获取
if [[ -z "${ocid:-}" ]]; then
log_info "正在获取 OCID..."
ocid=$(oci iam availability-domain list --query 'data[0]."compartment-id"' --raw-output 2>/dev/null)
check_not_empty "$ocid" "ocid"
fi
log_success "OCID 获取成功: $ocid"
# 网络配置
if [[ -z "${subnet_id:-}" ]]; then
log_info "正在配置网络..."
local fifo
fifo=$(mktemp -u)
mkfifo "$fifo"
trap 'rm -f "$fifo"' EXIT
configure_network "$ocid" "$fifo" &
read subnet_id < "$fifo"
log_success "网络配置完成 (子网ID: $subnet_id)"
else
log_info "使用指定的子网ID: $subnet_id"
fi
# 生成配置
local shape_cfg
shape_cfg=$(generate_shape_config "$shape_config")
local image_id
image_id=$(oci compute image list \
--compartment-id "$ocid" \
--display-name "$image_name" \
--query 'data[0].id' \
--raw-output 2>/dev/null)
check_return $? "镜像查询失败"
local source_cfg
if [[ $boot_size -eq 0 ]] || [[ $boot_size -lt 50 ]]; then
source_cfg="{\"sourceType\":\"image\",\"imageId\":\"$image_id\",\"bootVolumeVpusPerGB\":$vpu}"
else
source_cfg="{\"sourceType\":\"image\",\"imageId\":\"$image_id\",\"bootVolumeVpusPerGB\":$vpu,\"bootVolumeSizeInGBs\":$boot_size}"
fi
local metadata="{\"user_data\":\"$(generate_user_data "$password")\"}"
local tags="{\"RootPassword\":\"$password\"}"
# 获取可用域
log_info "正在获取可用域列表..."
local domains
domains=$(oci iam availability-domain list \
--compartment-id "$ocid" \
--query 'data[*].name' \
--raw-output 2>/dev/null)
check_return $? "无法获取可用域"
domains=$(echo "$domains" | jq -c | tr -d '[]"' | tr ',' ' ')
read -ra domains <<< "$domains"
if [[ ${#domains[@]} -eq 0 ]]; then
log_error "未找到可用域"
exit 1
fi
local target_domain="${domains[$domain]}"
log_success "选择可用域: $target_domain"
# 批量创建实例
log_info "开始批量创建实例 (数量: $number)..."
local instance_id
for ((i=1; i<=number; i++)); do
log_info "正在创建第 $i/$number 个实例..."
instance_id=$(oci compute instance launch \
--compartment-id "$ocid" \
--display-name "instance-$(date +%Y%m%d-%H%M%S)" \
--shape "$shape" \
--shape-config "$shape_cfg" \
--subnet-id "$subnet_id" \
--assign-ipv6-ip true \
--source-details "$source_cfg" \
--availability-domain "$target_domain" \
--metadata "$metadata" \
--freeform-tags "$tags" \
--query 'data."id"' \
--raw-output)
check_return $? "实例创建失败"
log_success "实例 $i/$number 创建成功 (ID: $instance_id)"
# 随机延迟避免 API 限制
sleep $((RANDOM % 5 + 1))
done
log_success "所有实例部署完成!"
}
# 执行主函数
main "$@"