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:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# Claude Code
|
||||
.mcp.json
|
||||
.claude/
|
||||
llmdoc/
|
||||
245
common/README.md
Normal file
245
common/README.md
Normal file
@@ -0,0 +1,245 @@
|
||||
# 公共函数库
|
||||
|
||||
本目录包含可复用的 Bash 函数库,用于标准化所有脚本的日志输出和错误处理。
|
||||
|
||||
## 📦 包含的库
|
||||
|
||||
### 1. logging.sh - 日志输出库
|
||||
|
||||
提供统一的日志输出函数,支持多个日志级别和彩色输出。
|
||||
|
||||
**功能特性:**
|
||||
- 5个日志级别:DEBUG, INFO, WARNING, ERROR, SUCCESS
|
||||
- 彩色控制台输出
|
||||
- 可选的时间戳
|
||||
- 支持输出到文件
|
||||
- 灵活的日志级别控制
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
source common/logging.sh
|
||||
|
||||
# 基本使用
|
||||
log_info "程序启动"
|
||||
log_success "操作完成"
|
||||
log_warning "磁盘空间不足"
|
||||
log_error "连接失败"
|
||||
log_debug "调试信息"
|
||||
|
||||
# 设置日志级别
|
||||
log_set_level "DEBUG" # 显示所有日志
|
||||
|
||||
# 输出到文件
|
||||
log_set_file "/var/log/myapp.log"
|
||||
|
||||
# 禁用时间戳
|
||||
log_disable_timestamp
|
||||
```
|
||||
|
||||
### 2. error_handler.sh - 错误处理库
|
||||
|
||||
提供统一的错误处理和检查机制。
|
||||
|
||||
**功能特性:**
|
||||
- 命令存在性检查
|
||||
- 文件/目录检查
|
||||
- 变量非空检查
|
||||
- Root 权限检查
|
||||
- 命令返回值检查
|
||||
- 带重试的命令执行
|
||||
- 调用栈跟踪
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
source common/error_handler.sh
|
||||
|
||||
# 启用严格错误处理
|
||||
enable_exit_on_error
|
||||
|
||||
# 检查命令是否存在
|
||||
check_command "git" || error_exit "git 未安装"
|
||||
|
||||
# 检查文件
|
||||
check_file "/etc/config" || error_exit "配置文件不存在"
|
||||
|
||||
# 检查 root 权限
|
||||
check_root || error_exit "需要 root 权限"
|
||||
|
||||
# 执行命令并检查结果
|
||||
run_command "安装失败" apt-get install -y package
|
||||
|
||||
# 带重试的命令执行
|
||||
retry_command 3 5 "下载失败" curl -O https://example.com/file
|
||||
|
||||
# 检查变量非空
|
||||
check_not_empty "$PROJECT_ID" "PROJECT_ID" || error_exit "项目ID不能为空"
|
||||
```
|
||||
|
||||
## 🚀 完整示例脚本
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# 示例脚本:展示如何使用公共函数库
|
||||
# ============================================================================
|
||||
|
||||
# 获取脚本目录
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# 加载公共库
|
||||
source "${SCRIPT_DIR}/../common/logging.sh"
|
||||
source "${SCRIPT_DIR}/../common/error_handler.sh"
|
||||
|
||||
# 配置日志
|
||||
log_set_level "INFO"
|
||||
log_set_file "/tmp/example.log"
|
||||
|
||||
# 启用错误处理
|
||||
enable_exit_on_error
|
||||
|
||||
#
|
||||
# 主函数
|
||||
#
|
||||
main() {
|
||||
log_info "脚本开始执行"
|
||||
|
||||
# 检查必要的命令
|
||||
check_command "git" || error_exit "请先安装 git"
|
||||
|
||||
# 检查参数
|
||||
local project_name="$1"
|
||||
check_not_empty "$project_name" "project_name" || {
|
||||
log_error "用法: $0 <project_name>"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 执行操作
|
||||
log_info "正在处理项目: $project_name"
|
||||
|
||||
# 带重试的网络操作
|
||||
retry_command 3 2 "网络请求失败" curl -f "https://example.com/api"
|
||||
|
||||
log_success "脚本执行完成"
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$@"
|
||||
```
|
||||
|
||||
## 📖 最佳实践
|
||||
|
||||
### 1. 脚本模板
|
||||
|
||||
每个脚本都应遵循以下模板结构:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -euo pipefail # 启用严格模式
|
||||
|
||||
# 脚本信息
|
||||
# ============================================================================
|
||||
# 文件名: script_name.sh
|
||||
# 描述: 脚本功能描述
|
||||
# 作者: 作者名
|
||||
# 版本: 1.0.0
|
||||
# ============================================================================
|
||||
|
||||
# 常量定义
|
||||
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly SCRIPT_NAME="$(basename "$0")"
|
||||
|
||||
# 加载公共库
|
||||
source "${SCRIPT_DIR}/../common/logging.sh"
|
||||
source "${SCRIPT_DIR}/../common/error_handler.sh"
|
||||
|
||||
# 全局变量
|
||||
GLOBAL_VAR=""
|
||||
|
||||
#
|
||||
# 显示使用帮助
|
||||
#
|
||||
show_usage() {
|
||||
cat << EOF
|
||||
使用方法: $SCRIPT_NAME [选项]
|
||||
|
||||
选项:
|
||||
-h, --help 显示此帮助信息
|
||||
-v, --verbose 启用详细输出
|
||||
|
||||
示例:
|
||||
$SCRIPT_NAME --verbose
|
||||
EOF
|
||||
}
|
||||
|
||||
#
|
||||
# 主函数
|
||||
#
|
||||
main() {
|
||||
log_info "脚本开始执行"
|
||||
|
||||
# 你的代码逻辑
|
||||
|
||||
log_success "脚本执行完成"
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$@"
|
||||
```
|
||||
|
||||
### 2. 错误处理建议
|
||||
|
||||
- 总是检查命令返回值
|
||||
- 使用 `check_*` 函数进行前置条件检查
|
||||
- 对网络操作使用 `retry_command`
|
||||
- 提供有意义的错误消息
|
||||
|
||||
### 3. 日志使用建议
|
||||
|
||||
- INFO: 正常流程信息
|
||||
- SUCCESS: 操作成功完成
|
||||
- WARNING: 需要注意但不影响执行的情况
|
||||
- ERROR: 错误但可以继续的情况
|
||||
- DEBUG: 调试信息(默认不显示)
|
||||
|
||||
## 🔧 环境变量配置
|
||||
|
||||
### logging.sh 环境变量
|
||||
|
||||
```bash
|
||||
# 设置日志级别 (DEBUG=0, INFO=1, WARNING=2, ERROR=3, SUCCESS=4)
|
||||
export LOG_CURRENT_LEVEL=1
|
||||
|
||||
# 设置日志文件路径
|
||||
export LOG_FILE_PATH="/var/log/myapp.log"
|
||||
|
||||
# 启用/禁用时间戳
|
||||
export LOG_ENABLE_TIMESTAMP=true
|
||||
```
|
||||
|
||||
### error_handler.sh 环境变量
|
||||
|
||||
```bash
|
||||
# 错误时是否退出 (true/false)
|
||||
export ERROR_EXIT_ON_FAIL=true
|
||||
|
||||
# 是否显示调用栈 (true/false)
|
||||
export ERROR_STACK_TRACE=false
|
||||
```
|
||||
|
||||
## 📝 维护说明
|
||||
|
||||
- 版本: 1.0.0
|
||||
- 最后更新: 2025-12-26
|
||||
- 维护者: Cloud Tools Project
|
||||
|
||||
## 🤝 贡献
|
||||
|
||||
如需添加新功能或修复 bug,请:
|
||||
1. 保持向后兼容性
|
||||
2. 添加完整的函数文档注释
|
||||
3. 提供使用示例
|
||||
4. 更新此 README
|
||||
179
common/demo_usage.sh
Executable file
179
common/demo_usage.sh
Executable file
@@ -0,0 +1,179 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# 文件名: demo_usage.sh
|
||||
# 描述: 公共函数库功能演示和使用示例
|
||||
# 作者: Cloud Tools Project
|
||||
# 版本: 1.0.0
|
||||
# ============================================================================
|
||||
|
||||
set -euo pipefail # 启用严格模式
|
||||
|
||||
# 获取脚本目录
|
||||
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# 加载公共库
|
||||
source "${SCRIPT_DIR}/logging.sh"
|
||||
source "${SCRIPT_DIR}/error_handler.sh"
|
||||
|
||||
#
|
||||
# 示例 1: 基本日志使用
|
||||
#
|
||||
example_basic_logging() {
|
||||
log_info "============ 示例 1: 基本日志使用 ============"
|
||||
|
||||
log_debug "这是调试信息(默认不显示)"
|
||||
log_info "这是普通信息"
|
||||
log_warning "这是警告信息"
|
||||
log_error "这是错误信息(但不会退出)"
|
||||
log_success "这是成功信息"
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
#
|
||||
# 示例 2: 日志级别控制
|
||||
#
|
||||
example_log_levels() {
|
||||
log_info "============ 示例 2: 日志级别控制 ============"
|
||||
|
||||
log_info "当前日志级别: INFO"
|
||||
log_debug "这条调试信息不会显示"
|
||||
|
||||
log_info "切换到 DEBUG 级别..."
|
||||
log_set_level "DEBUG"
|
||||
log_debug "现在调试信息可以显示了!"
|
||||
|
||||
log_info "恢复到 INFO 级别..."
|
||||
log_set_level "INFO"
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
#
|
||||
# 示例 3: 错误检查
|
||||
#
|
||||
example_error_checking() {
|
||||
log_info "============ 示例 3: 错误检查 ============"
|
||||
|
||||
# 临时禁用自动退出
|
||||
disable_exit_on_error
|
||||
|
||||
# 检查命令
|
||||
if check_command "bash"; then
|
||||
log_success "bash 命令存在"
|
||||
fi
|
||||
|
||||
if ! check_command "nonexistent_command"; then
|
||||
log_warning "nonexistent_command 不存在(这是预期的)"
|
||||
fi
|
||||
|
||||
# 检查文件
|
||||
if check_file "${SCRIPT_DIR}/logging.sh"; then
|
||||
log_success "logging.sh 文件存在"
|
||||
fi
|
||||
|
||||
# 检查变量非空
|
||||
local test_var="hello"
|
||||
if check_not_empty "$test_var" "test_var"; then
|
||||
log_success "test_var 变量非空"
|
||||
fi
|
||||
|
||||
# 重新启用自动退出
|
||||
enable_exit_on_error
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
#
|
||||
# 示例 4: 命令执行和重试
|
||||
#
|
||||
example_command_execution() {
|
||||
log_info "============ 示例 4: 命令执行 ============"
|
||||
|
||||
# 执行简单命令
|
||||
run_command "列出文件失败" ls -la "${SCRIPT_DIR}"
|
||||
|
||||
log_info "执行成功的命令"
|
||||
|
||||
# 临时禁用自动退出来演示重试
|
||||
disable_exit_on_error
|
||||
|
||||
log_info "尝试执行一个会失败的命令(演示重试)"
|
||||
retry_command 3 1 "命令失败" false
|
||||
|
||||
# 重新启用
|
||||
enable_exit_on_error
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
#
|
||||
# 示例 5: 日志文件输出
|
||||
#
|
||||
example_log_file() {
|
||||
log_info "============ 示例 5: 日志文件输出 ============"
|
||||
|
||||
local temp_log="/tmp/common_lib_example_$$.log"
|
||||
|
||||
log_info "设置日志输出到文件: $temp_log"
|
||||
log_set_file "$temp_log"
|
||||
|
||||
log_info "这条消息会同时输出到控制台和文件"
|
||||
log_success "日志文件功能正常"
|
||||
|
||||
log_info "日志文件内容:"
|
||||
cat "$temp_log"
|
||||
|
||||
# 清理
|
||||
rm -f "$temp_log"
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
#
|
||||
# 示例 6: 时间戳控制
|
||||
#
|
||||
example_timestamp() {
|
||||
log_info "============ 示例 6: 时间戳控制 ============"
|
||||
|
||||
log_info "默认启用时间戳"
|
||||
|
||||
log_info "禁用时间戳..."
|
||||
log_disable_timestamp
|
||||
log_info "这条消息没有时间戳"
|
||||
|
||||
log_info "启用时间戳..."
|
||||
log_enable_timestamp
|
||||
log_info "时间戳已恢复"
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
#
|
||||
# 主函数
|
||||
#
|
||||
main() {
|
||||
# 配置日志
|
||||
log_set_level "INFO"
|
||||
log_enable_timestamp
|
||||
|
||||
log_success "==================================================="
|
||||
log_success " 公共函数库使用示例"
|
||||
log_success "==================================================="
|
||||
echo ""
|
||||
|
||||
# 执行所有示例
|
||||
example_basic_logging
|
||||
example_log_levels
|
||||
example_error_checking
|
||||
example_command_execution
|
||||
example_log_file
|
||||
example_timestamp
|
||||
|
||||
log_success "==================================================="
|
||||
log_success " 所有示例执行完成!"
|
||||
log_success "==================================================="
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$@"
|
||||
300
common/error_handler.sh
Normal file
300
common/error_handler.sh
Normal file
@@ -0,0 +1,300 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# 文件名: error_handler.sh
|
||||
# 描述: 统一的错误处理机制
|
||||
# 作者: Cloud Tools Project
|
||||
# 版本: 1.0.0
|
||||
# 依赖: logging.sh
|
||||
# 使用方法: source common/error_handler.sh
|
||||
# ============================================================================
|
||||
|
||||
# 确保加载日志库
|
||||
if ! declare -f log_error >/dev/null 2>&1; then
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=./logging.sh
|
||||
source "${SCRIPT_DIR}/logging.sh"
|
||||
fi
|
||||
|
||||
# 错误处理配置
|
||||
ERROR_EXIT_ON_FAIL=${ERROR_EXIT_ON_FAIL:-true}
|
||||
ERROR_STACK_TRACE=${ERROR_STACK_TRACE:-false}
|
||||
|
||||
#
|
||||
# 内部: 获取调用栈信息
|
||||
# 返回: 格式化的调用栈字符串
|
||||
#
|
||||
_error_get_stack_trace() {
|
||||
local frame=0
|
||||
local line
|
||||
local func
|
||||
local src
|
||||
|
||||
echo "调用栈:"
|
||||
while caller $frame; do
|
||||
((frame++))
|
||||
done | while read -r line func src; do
|
||||
echo " at $func ($src:$line)"
|
||||
done
|
||||
}
|
||||
|
||||
#
|
||||
# 检查命令是否存在
|
||||
# 参数: $1 - 命令名称
|
||||
# 返回: 0=存在, 1=不存在
|
||||
# 示例: check_command "git" || error_exit "git 未安装"
|
||||
#
|
||||
check_command() {
|
||||
local cmd="$1"
|
||||
|
||||
if ! command -v "$cmd" &>/dev/null; then
|
||||
log_error "命令不存在: $cmd"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
#
|
||||
# 检查文件是否存在
|
||||
# 参数: $1 - 文件路径
|
||||
# 返回: 0=存在, 1=不存在
|
||||
# 示例: check_file "/path/to/file" || error_exit "文件不存在"
|
||||
#
|
||||
check_file() {
|
||||
local file="$1"
|
||||
|
||||
if [[ ! -f "$file" ]]; then
|
||||
log_error "文件不存在: $file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
#
|
||||
# 检查目录是否存在
|
||||
# 参数: $1 - 目录路径
|
||||
# 返回: 0=存在, 1=不存在
|
||||
# 示例: check_directory "/path/to/dir" || error_exit "目录不存在"
|
||||
#
|
||||
check_directory() {
|
||||
local dir="$1"
|
||||
|
||||
if [[ ! -d "$dir" ]]; then
|
||||
log_error "目录不存在: $dir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
#
|
||||
# 检查变量是否为空
|
||||
# 参数:
|
||||
# $1 - 变量值
|
||||
# $2 - 变量名称 (可选,用于错误消息)
|
||||
# 返回: 0=非空, 1=为空
|
||||
# 示例: check_not_empty "$VAR" "VAR" || error_exit "变量不能为空"
|
||||
#
|
||||
check_not_empty() {
|
||||
local value="$1"
|
||||
local var_name="${2:-变量}"
|
||||
|
||||
if [[ -z "$value" ]]; then
|
||||
log_error "${var_name} 不能为空"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
#
|
||||
# 检查是否以 root 权限运行
|
||||
# 返回: 0=是root, 1=不是root
|
||||
# 示例: check_root || error_exit "此脚本需要 root 权限"
|
||||
#
|
||||
check_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
log_error "此脚本需要 root 权限运行"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
#
|
||||
# 检查命令返回值
|
||||
# 参数:
|
||||
# $1 - 命令返回值 ($?)
|
||||
# $2 - 错误消息 (可选)
|
||||
# 返回: 传入的返回值
|
||||
# 示例: some_command; check_return $? "命令执行失败"
|
||||
#
|
||||
check_return() {
|
||||
local ret=$1
|
||||
local msg="${2:-命令执行失败}"
|
||||
|
||||
if [[ $ret -ne 0 ]]; then
|
||||
log_error "$msg (退出码: $ret)"
|
||||
|
||||
if [[ "$ERROR_STACK_TRACE" == "true" ]]; then
|
||||
_error_get_stack_trace >&2
|
||||
fi
|
||||
|
||||
if [[ "$ERROR_EXIT_ON_FAIL" == "true" ]]; then
|
||||
exit "$ret"
|
||||
fi
|
||||
fi
|
||||
|
||||
return "$ret"
|
||||
}
|
||||
|
||||
#
|
||||
# 错误退出
|
||||
# 参数:
|
||||
# $1 - 错误消息
|
||||
# $2 - 退出码 (可选,默认: 1)
|
||||
# 示例: error_exit "配置文件不存在" 2
|
||||
#
|
||||
error_exit() {
|
||||
local msg="$1"
|
||||
local exit_code="${2:-1}"
|
||||
|
||||
log_error "$msg"
|
||||
|
||||
if [[ "$ERROR_STACK_TRACE" == "true" ]]; then
|
||||
_error_get_stack_trace >&2
|
||||
fi
|
||||
|
||||
exit "$exit_code"
|
||||
}
|
||||
|
||||
#
|
||||
# 执行命令并检查结果
|
||||
# 参数:
|
||||
# $1 - 错误消息前缀
|
||||
# $@ - 要执行的命令和参数
|
||||
# 返回: 命令的返回值
|
||||
# 示例: run_command "安装失败" apt-get install -y package
|
||||
#
|
||||
run_command() {
|
||||
local error_msg="$1"
|
||||
shift
|
||||
|
||||
log_debug "执行命令: $*"
|
||||
|
||||
if ! "$@"; then
|
||||
local ret=$?
|
||||
log_error "${error_msg}: $*"
|
||||
|
||||
if [[ "$ERROR_EXIT_ON_FAIL" == "true" ]]; then
|
||||
exit "$ret"
|
||||
fi
|
||||
|
||||
return "$ret"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
#
|
||||
# 带重试的命令执行
|
||||
# 参数:
|
||||
# $1 - 最大重试次数
|
||||
# $2 - 重试间隔 (秒)
|
||||
# $3 - 错误消息前缀
|
||||
# $@ - 要执行的命令和参数
|
||||
# 返回: 命令的返回值
|
||||
# 示例: retry_command 3 5 "连接失败" curl -f https://example.com
|
||||
#
|
||||
retry_command() {
|
||||
local max_retries=$1
|
||||
local retry_delay=$2
|
||||
local error_msg="$3"
|
||||
shift 3
|
||||
|
||||
local attempt=1
|
||||
local ret
|
||||
|
||||
while [[ $attempt -le $max_retries ]]; do
|
||||
log_debug "尝试执行 (第 $attempt/$max_retries 次): $*"
|
||||
|
||||
if "$@"; then
|
||||
log_debug "命令执行成功"
|
||||
return 0
|
||||
fi
|
||||
|
||||
ret=$?
|
||||
|
||||
if [[ $attempt -lt $max_retries ]]; then
|
||||
log_warning "${error_msg} (第 $attempt 次失败,将在 ${retry_delay}秒后重试)"
|
||||
sleep "$retry_delay"
|
||||
else
|
||||
log_error "${error_msg} (已重试 $max_retries 次)"
|
||||
fi
|
||||
|
||||
((attempt++))
|
||||
done
|
||||
|
||||
if [[ "$ERROR_EXIT_ON_FAIL" == "true" ]]; then
|
||||
exit "$ret"
|
||||
fi
|
||||
|
||||
return "$ret"
|
||||
}
|
||||
|
||||
#
|
||||
# 启用错误时退出
|
||||
# 示例: enable_exit_on_error
|
||||
#
|
||||
enable_exit_on_error() {
|
||||
ERROR_EXIT_ON_FAIL=true
|
||||
set -e
|
||||
set -o pipefail
|
||||
}
|
||||
|
||||
#
|
||||
# 禁用错误时退出
|
||||
# 示例: disable_exit_on_error
|
||||
#
|
||||
disable_exit_on_error() {
|
||||
ERROR_EXIT_ON_FAIL=false
|
||||
set +e
|
||||
set +o pipefail
|
||||
}
|
||||
|
||||
#
|
||||
# 启用调用栈跟踪
|
||||
# 示例: enable_stack_trace
|
||||
#
|
||||
enable_stack_trace() {
|
||||
ERROR_STACK_TRACE=true
|
||||
}
|
||||
|
||||
#
|
||||
# 禁用调用栈跟踪
|
||||
# 示例: disable_stack_trace
|
||||
#
|
||||
disable_stack_trace() {
|
||||
ERROR_STACK_TRACE=false
|
||||
}
|
||||
|
||||
# 设置 ERR 陷阱(可选)
|
||||
# trap '_error_handler $? $LINENO' ERR
|
||||
|
||||
#
|
||||
# 内部: ERR 陷阱处理函数
|
||||
# 参数:
|
||||
# $1 - 错误码
|
||||
# $2 - 行号
|
||||
#
|
||||
_error_handler() {
|
||||
local exit_code=$1
|
||||
local line_number=$2
|
||||
|
||||
log_error "脚本在第 $line_number 行发生错误 (退出码: $exit_code)"
|
||||
|
||||
if [[ "$ERROR_STACK_TRACE" == "true" ]]; then
|
||||
_error_get_stack_trace >&2
|
||||
fi
|
||||
}
|
||||
199
common/logging.sh
Normal file
199
common/logging.sh
Normal file
@@ -0,0 +1,199 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# 文件名: logging.sh
|
||||
# 描述: 统一的日志输出函数库
|
||||
# 作者: Cloud Tools Project
|
||||
# 版本: 1.0.0
|
||||
# 依赖: 无
|
||||
# 使用方法: source common/logging.sh
|
||||
# ============================================================================
|
||||
|
||||
# 颜色定义 (ANSI 颜色代码)
|
||||
readonly LOG_COLOR_RED='\033[0;31m'
|
||||
readonly LOG_COLOR_GREEN='\033[0;32m'
|
||||
readonly LOG_COLOR_YELLOW='\033[1;33m'
|
||||
readonly LOG_COLOR_BLUE='\033[0;34m'
|
||||
readonly LOG_COLOR_MAGENTA='\033[0;35m'
|
||||
readonly LOG_COLOR_CYAN='\033[0;36m'
|
||||
readonly LOG_COLOR_RESET='\033[0m'
|
||||
|
||||
# 日志级别定义
|
||||
readonly LOG_LEVEL_DEBUG=0
|
||||
readonly LOG_LEVEL_INFO=1
|
||||
readonly LOG_LEVEL_WARNING=2
|
||||
readonly LOG_LEVEL_ERROR=3
|
||||
readonly LOG_LEVEL_SUCCESS=4
|
||||
|
||||
# 当前日志级别 (默认: INFO)
|
||||
LOG_CURRENT_LEVEL=${LOG_CURRENT_LEVEL:-$LOG_LEVEL_INFO}
|
||||
|
||||
# 日志文件路径 (可选)
|
||||
LOG_FILE_PATH="${LOG_FILE_PATH:-}"
|
||||
|
||||
# 是否启用时间戳
|
||||
LOG_ENABLE_TIMESTAMP=${LOG_ENABLE_TIMESTAMP:-true}
|
||||
|
||||
#
|
||||
# 获取格式化的时间戳
|
||||
# 返回: 格式化的时间字符串
|
||||
#
|
||||
_log_get_timestamp() {
|
||||
date '+%Y-%m-%d %H:%M:%S'
|
||||
}
|
||||
|
||||
#
|
||||
# 内部日志输出函数
|
||||
# 参数:
|
||||
# $1 - 日志级别 (DEBUG|INFO|WARNING|ERROR|SUCCESS)
|
||||
# $2 - 日志颜色
|
||||
# $3 - 日志消息
|
||||
# $4 - 输出目标 (stdout/stderr)
|
||||
#
|
||||
_log_output() {
|
||||
local level="$1"
|
||||
local color="$2"
|
||||
local message="$3"
|
||||
local target="${4:-stdout}"
|
||||
|
||||
local timestamp=""
|
||||
if [[ "$LOG_ENABLE_TIMESTAMP" == "true" ]]; then
|
||||
timestamp="$(_log_get_timestamp) "
|
||||
fi
|
||||
|
||||
local log_line="${timestamp}[${level}] ${message}"
|
||||
local colored_line="${color}${log_line}${LOG_COLOR_RESET}"
|
||||
|
||||
# 输出到控制台
|
||||
if [[ "$target" == "stderr" ]]; then
|
||||
echo -e "$colored_line" >&2
|
||||
else
|
||||
echo -e "$colored_line"
|
||||
fi
|
||||
|
||||
# 输出到日志文件 (如果配置了)
|
||||
if [[ -n "$LOG_FILE_PATH" ]]; then
|
||||
echo "$log_line" >> "$LOG_FILE_PATH"
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# 调试级别日志
|
||||
# 参数: $1 - 日志消息
|
||||
# 示例: log_debug "Debugging information"
|
||||
#
|
||||
log_debug() {
|
||||
if [[ $LOG_CURRENT_LEVEL -le $LOG_LEVEL_DEBUG ]]; then
|
||||
_log_output "DEBUG" "$LOG_COLOR_CYAN" "$1" "stdout"
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# 信息级别日志
|
||||
# 参数: $1 - 日志消息
|
||||
# 示例: log_info "Process started"
|
||||
#
|
||||
log_info() {
|
||||
if [[ $LOG_CURRENT_LEVEL -le $LOG_LEVEL_INFO ]]; then
|
||||
_log_output "INFO" "$LOG_COLOR_BLUE" "$1" "stdout"
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# 警告级别日志
|
||||
# 参数: $1 - 日志消息
|
||||
# 示例: log_warning "Disk space low"
|
||||
#
|
||||
log_warning() {
|
||||
if [[ $LOG_CURRENT_LEVEL -le $LOG_LEVEL_WARNING ]]; then
|
||||
_log_output "WARNING" "$LOG_COLOR_YELLOW" "$1" "stderr"
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# 错误级别日志
|
||||
# 参数: $1 - 日志消息
|
||||
# 示例: log_error "Failed to connect to server"
|
||||
#
|
||||
log_error() {
|
||||
if [[ $LOG_CURRENT_LEVEL -le $LOG_LEVEL_ERROR ]]; then
|
||||
_log_output "ERROR" "$LOG_COLOR_RED" "$1" "stderr"
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# 成功级别日志
|
||||
# 参数: $1 - 日志消息
|
||||
# 示例: log_success "Deployment completed"
|
||||
#
|
||||
log_success() {
|
||||
if [[ $LOG_CURRENT_LEVEL -le $LOG_LEVEL_SUCCESS ]]; then
|
||||
_log_output "SUCCESS" "$LOG_COLOR_GREEN" "$1" "stdout"
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# 设置日志级别
|
||||
# 参数: $1 - 日志级别 (DEBUG|INFO|WARNING|ERROR|SUCCESS)
|
||||
# 示例: log_set_level "DEBUG"
|
||||
#
|
||||
log_set_level() {
|
||||
local level
|
||||
level="$(echo "$1" | tr '[:lower:]' '[:upper:]')"
|
||||
|
||||
case "$level" in
|
||||
DEBUG) LOG_CURRENT_LEVEL=$LOG_LEVEL_DEBUG ;;
|
||||
INFO) LOG_CURRENT_LEVEL=$LOG_LEVEL_INFO ;;
|
||||
WARNING) LOG_CURRENT_LEVEL=$LOG_LEVEL_WARNING ;;
|
||||
ERROR) LOG_CURRENT_LEVEL=$LOG_LEVEL_ERROR ;;
|
||||
SUCCESS) LOG_CURRENT_LEVEL=$LOG_LEVEL_SUCCESS ;;
|
||||
*)
|
||||
log_error "Invalid log level: $1"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
#
|
||||
# 设置日志文件路径
|
||||
# 参数: $1 - 日志文件完整路径
|
||||
# 示例: log_set_file "/var/log/myapp.log"
|
||||
#
|
||||
log_set_file() {
|
||||
LOG_FILE_PATH="$1"
|
||||
|
||||
# 创建日志文件目录
|
||||
local log_dir
|
||||
log_dir="$(dirname "$LOG_FILE_PATH")"
|
||||
if [[ ! -d "$log_dir" ]]; then
|
||||
mkdir -p "$log_dir" 2>/dev/null || {
|
||||
log_error "无法创建日志目录: $log_dir"
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
|
||||
# 初始化日志文件
|
||||
if [[ ! -f "$LOG_FILE_PATH" ]]; then
|
||||
touch "$LOG_FILE_PATH" 2>/dev/null || {
|
||||
log_error "无法创建日志文件: $LOG_FILE_PATH"
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
|
||||
log_info "日志输出到文件: $LOG_FILE_PATH"
|
||||
}
|
||||
|
||||
#
|
||||
# 禁用时间戳
|
||||
# 示例: log_disable_timestamp
|
||||
#
|
||||
log_disable_timestamp() {
|
||||
LOG_ENABLE_TIMESTAMP=false
|
||||
}
|
||||
|
||||
#
|
||||
# 启用时间戳
|
||||
# 示例: log_enable_timestamp
|
||||
#
|
||||
log_enable_timestamp() {
|
||||
LOG_ENABLE_TIMESTAMP=true
|
||||
}
|
||||
514
gcp/create_ai_projects.sh
Normal file
514
gcp/create_ai_projects.sh
Normal file
@@ -0,0 +1,514 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# 文件名: create_ai_projects.sh
|
||||
# 描述: 批量创建 Google Cloud Platform AI 项目并配置相关服务
|
||||
# 作者: 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_PROJECT_ID_PREFIX="project"
|
||||
readonly DEFAULT_START_NUM=1
|
||||
readonly DEFAULT_REPEAT_NUM=5
|
||||
readonly DEFAULT_MAX_RETRIES=3
|
||||
readonly DEFAULT_RETRY_DELAY=5
|
||||
|
||||
# 创建日志文件
|
||||
readonly LOG_FILE="created_projects_$(date +%Y%m%d_%H%M%S).log"
|
||||
log_set_file "$LOG_FILE"
|
||||
|
||||
#
|
||||
# 显示脚本使用方法
|
||||
#
|
||||
usage() {
|
||||
log_info "用法: $0 [选项]"
|
||||
log_info "选项:"
|
||||
log_info " -p, --prefix PREFIX 项目ID前缀 (默认: $DEFAULT_PROJECT_ID_PREFIX)"
|
||||
log_info " -s, --start START 项目编号起始值 (默认: $DEFAULT_START_NUM)"
|
||||
log_info " -n, --number NUMBER 创建项目的数量 (默认: $DEFAULT_REPEAT_NUM)"
|
||||
log_info " --max-retries RETRIES 单个命令失败时的最大重试次数 (默认: $DEFAULT_MAX_RETRIES)"
|
||||
log_info " --retry-delay DELAY 每次重试之间的延迟秒数 (默认: $DEFAULT_RETRY_DELAY)"
|
||||
log_info " --debug 启用调试模式"
|
||||
log_info " -h, --help 显示此帮助信息"
|
||||
}
|
||||
|
||||
#
|
||||
# 检查必要的命令是否存在
|
||||
#
|
||||
check_command() {
|
||||
if ! command -v "$1" &> /dev/null; then
|
||||
log_error "命令 $1 未找到。请确保它已安装并在PATH中。"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# 重试执行命令
|
||||
#
|
||||
retry_command() {
|
||||
local max_attempts=$1
|
||||
local delay=$2
|
||||
shift 2
|
||||
local cmd_to_run=("$@")
|
||||
local attempt=1
|
||||
local exit_code
|
||||
local temp_output
|
||||
local temp_error
|
||||
|
||||
while [ $attempt -le "$max_attempts" ]; do
|
||||
log_info "尝试执行 (第 $attempt/$max_attempts 次): ${cmd_to_run[*]}"
|
||||
|
||||
# 创建临时文件捕获输出和错误
|
||||
temp_output=$(mktemp)
|
||||
temp_error=$(mktemp)
|
||||
|
||||
if "${cmd_to_run[@]}" > "$temp_output" 2> "$temp_error"; then
|
||||
log_success "命令成功执行: ${cmd_to_run[*]}"
|
||||
# 如果有输出,显示它
|
||||
if [ -s "$temp_output" ]; then
|
||||
cat "$temp_output"
|
||||
fi
|
||||
rm -f "$temp_output" "$temp_error" 2>/dev/null || true
|
||||
return 0
|
||||
else
|
||||
exit_code=$?
|
||||
log_warning "命令执行失败 (第 $attempt/$max_attempts 次),退出码: $exit_code"
|
||||
|
||||
# 显示错误信息用于调试
|
||||
if [ -s "$temp_error" ]; then
|
||||
log_error "错误输出: $(cat "$temp_error")"
|
||||
fi
|
||||
|
||||
if [ $attempt -lt "$max_attempts" ]; then
|
||||
log_info "将在 $delay 秒后重试..."
|
||||
sleep "$delay"
|
||||
fi
|
||||
fi
|
||||
rm -f "$temp_output" "$temp_error" 2>/dev/null || true
|
||||
((attempt++))
|
||||
done
|
||||
|
||||
log_error "命令在 $max_attempts 次尝试后仍然失败: ${cmd_to_run[*]}"
|
||||
return 1
|
||||
}
|
||||
|
||||
#
|
||||
# 重试并获取命令输出
|
||||
#
|
||||
retry_command_with_output() {
|
||||
local max_attempts=$1
|
||||
local delay=$2
|
||||
local output_var=$3
|
||||
shift 3
|
||||
local cmd_to_run=("$@")
|
||||
local attempt=1
|
||||
local exit_code
|
||||
local temp_output
|
||||
local temp_error
|
||||
|
||||
while [ $attempt -le "$max_attempts" ]; do
|
||||
log_info "尝试执行 (第 $attempt/$max_attempts 次): ${cmd_to_run[*]}"
|
||||
|
||||
temp_output=$(mktemp)
|
||||
temp_error=$(mktemp)
|
||||
|
||||
if "${cmd_to_run[@]}" > "$temp_output" 2> "$temp_error"; then
|
||||
log_success "命令成功执行: ${cmd_to_run[*]}"
|
||||
# 将输出赋值给指定变量
|
||||
eval "$output_var=\$(cat '$temp_output')"
|
||||
rm -f "$temp_output" "$temp_error" 2>/dev/null || true
|
||||
return 0
|
||||
else
|
||||
exit_code=$?
|
||||
log_warning "命令执行失败 (第 $attempt/$max_attempts 次),退出码: $exit_code"
|
||||
|
||||
if [ -s "$temp_error" ]; then
|
||||
log_error "错误输出: $(cat "$temp_error")"
|
||||
fi
|
||||
|
||||
if [ $attempt -lt "$max_attempts" ]; then
|
||||
log_info "将在 $delay 秒后重试..."
|
||||
sleep "$delay"
|
||||
fi
|
||||
fi
|
||||
rm -f "$temp_output" "$temp_error" 2>/dev/null || true
|
||||
((attempt++))
|
||||
done
|
||||
|
||||
log_error "命令在 $max_attempts 次尝试后仍然失败: ${cmd_to_run[*]}"
|
||||
return 1
|
||||
}
|
||||
|
||||
#
|
||||
# 创建 GCP 项目
|
||||
#
|
||||
create_project() {
|
||||
local project_id=$1
|
||||
local max_retries=$2
|
||||
local retry_delay=$3
|
||||
|
||||
log_info "创建项目: $project_id"
|
||||
|
||||
# 检查项目是否已存在
|
||||
if gcloud projects describe "$project_id" &>/dev/null; then
|
||||
log_warning "项目 $project_id 已存在,跳过创建"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if retry_command "$max_retries" "$retry_delay" gcloud projects create "$project_id" --name="$project_id"; then
|
||||
log_success "项目 $project_id 创建成功"
|
||||
return 0
|
||||
else
|
||||
log_error "创建项目 $project_id 失败 (已重试)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# 链接结算账号
|
||||
#
|
||||
link_billing() {
|
||||
local project_id=$1
|
||||
local billing_id=$2
|
||||
local max_retries=$3
|
||||
local retry_delay=$4
|
||||
|
||||
log_info "链接结算账号到项目: $project_id"
|
||||
if retry_command "$max_retries" "$retry_delay" gcloud beta billing projects link "$project_id" --billing-account="$billing_id"; then
|
||||
log_success "结算账号链接成功"
|
||||
return 0
|
||||
else
|
||||
log_error "链接结算账号到项目 $project_id 失败 (已重试)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# 启用服务
|
||||
#
|
||||
enable_services() {
|
||||
local project_id=$1
|
||||
local max_retries=$2
|
||||
local retry_delay=$3
|
||||
|
||||
log_info "启用 aiplatform.googleapis.com 服务"
|
||||
if retry_command "$max_retries" "$retry_delay" gcloud services enable aiplatform.googleapis.com --project="$project_id"; then
|
||||
log_success "服务启用成功"
|
||||
return 0
|
||||
else
|
||||
log_error "启用 aiplatform.googleapis.com 服务失败 (已重试)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# 创建服务账号
|
||||
#
|
||||
create_service_account() {
|
||||
local project_id=$1
|
||||
local max_retries=$2
|
||||
local retry_delay=$3
|
||||
|
||||
log_info "创建服务账号 service-account@$project_id.iam.gserviceaccount.com"
|
||||
|
||||
# 检查服务账号是否已存在
|
||||
if gcloud iam service-accounts describe "service-account@$project_id.iam.gserviceaccount.com" --project="$project_id" &>/dev/null; then
|
||||
log_warning "服务账号已存在,跳过创建"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if retry_command "$max_retries" "$retry_delay" gcloud iam service-accounts create service-account \
|
||||
--display-name="AI Platform Service Account" \
|
||||
--project="$project_id"; then
|
||||
log_success "服务账号创建成功"
|
||||
return 0
|
||||
else
|
||||
log_error "创建服务账号失败 (已重试)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# 添加 IAM 策略
|
||||
#
|
||||
add_iam_policy() {
|
||||
local project_id=$1
|
||||
local max_retries=$2
|
||||
local retry_delay=$3
|
||||
|
||||
log_info "授予 aiplatform.serviceAgent 角色"
|
||||
if retry_command "$max_retries" "$retry_delay" gcloud projects add-iam-policy-binding "$project_id" \
|
||||
--member="serviceAccount:service-account@$project_id.iam.gserviceaccount.com" \
|
||||
--role="roles/aiplatform.serviceAgent"; then
|
||||
log_success "IAM 策略授予成功"
|
||||
return 0
|
||||
else
|
||||
log_error "授予 IAM 策略失败 (已重试)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# 创建服务账号密钥
|
||||
#
|
||||
create_service_account_key() {
|
||||
local project_id=$1
|
||||
local max_retries=$2
|
||||
local retry_delay=$3
|
||||
|
||||
log_info "创建服务账号密钥"
|
||||
|
||||
# 检查密钥文件是否已存在
|
||||
if [ -f "pass-$project_id.json" ]; then
|
||||
log_warning "密钥文件 pass-$project_id.json 已存在,跳过创建"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if retry_command "$max_retries" "$retry_delay" gcloud iam service-accounts keys create "pass-$project_id.json" \
|
||||
--iam-account="service-account@$project_id.iam.gserviceaccount.com" \
|
||||
--project="$project_id"; then
|
||||
log_success "服务账号密钥创建成功: pass-$project_id.json"
|
||||
return 0
|
||||
else
|
||||
log_error "创建服务账号密钥失败 (已重试)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 参数解析和验证
|
||||
PROJECT_ID_PREFIX="$DEFAULT_PROJECT_ID_PREFIX"
|
||||
START_NUM="$DEFAULT_START_NUM"
|
||||
REPEAT_NUM="$DEFAULT_REPEAT_NUM"
|
||||
MAX_RETRIES="$DEFAULT_MAX_RETRIES"
|
||||
RETRY_DELAY="$DEFAULT_RETRY_DELAY"
|
||||
|
||||
# 解析命令行参数
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-p|--prefix)
|
||||
PROJECT_ID_PREFIX="$2"
|
||||
shift 2
|
||||
;;
|
||||
-s|--start)
|
||||
START_NUM="$2"
|
||||
shift 2
|
||||
;;
|
||||
-n|--number)
|
||||
REPEAT_NUM="$2"
|
||||
shift 2
|
||||
;;
|
||||
--max-retries)
|
||||
MAX_RETRIES="$2"
|
||||
shift 2
|
||||
;;
|
||||
--retry-delay)
|
||||
RETRY_DELAY="$2"
|
||||
shift 2
|
||||
;;
|
||||
--debug)
|
||||
DEBUG_MODE=true
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log_error "未知选项: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 检查必要的命令
|
||||
check_command gcloud
|
||||
|
||||
# 验证参数
|
||||
if ! [[ "$START_NUM" =~ ^[0-9]+$ ]]; then
|
||||
log_error "起始编号必须是一个正整数"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [[ "$REPEAT_NUM" =~ ^[0-9]+$ ]] || [ "$REPEAT_NUM" -lt 1 ]; then
|
||||
log_error "项目数量必须是一个正整数"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [[ "$MAX_RETRIES" =~ ^[0-9]+$ ]] || [ "$MAX_RETRIES" -lt 0 ]; then
|
||||
log_error "最大重试次数必须是一个非负整数"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [[ "$RETRY_DELAY" =~ ^[0-9]+$ ]] || [ "$RETRY_DELAY" -lt 0 ]; then
|
||||
log_error "重试延迟必须是一个非负整数"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "################################################################################"
|
||||
log_info "开始创建 GCP 项目批量任务"
|
||||
log_info "################################################################################"
|
||||
|
||||
# 调试模式提示
|
||||
if [ "$DEBUG_MODE" = true ]; then
|
||||
log_debug "调试模式已启用"
|
||||
fi
|
||||
|
||||
# 获取结算账号
|
||||
log_info "获取结算账号..."
|
||||
billing_id=""
|
||||
if retry_command_with_output "$MAX_RETRIES" "$RETRY_DELAY" billing_id_output gcloud beta billing accounts list --format="value(name.basename())" --filter="open=true"; then
|
||||
# 提取第一个结算账号ID
|
||||
billing_id=$(echo "$billing_id_output" | head -n 1 | tr -d '[:space:]')
|
||||
|
||||
if [ -z "$billing_id" ]; then
|
||||
log_error "未找到有效的活动结算账号。请检查您的结算账户。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "结算账号: $billing_id"
|
||||
else
|
||||
log_error "获取结算账号失败 (已重试)。请检查您的结算账户。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 计算结束编号
|
||||
END_NUM=$((START_NUM + REPEAT_NUM - 1))
|
||||
|
||||
log_info "配置信息:"
|
||||
log_info " 项目前缀: $PROJECT_ID_PREFIX"
|
||||
log_info " 起始编号: $START_NUM"
|
||||
log_info " 结束编号: $END_NUM"
|
||||
log_info " 项目总数: $REPEAT_NUM"
|
||||
log_info " 最大重试: $MAX_RETRIES 次"
|
||||
log_info " 重试延迟: $RETRY_DELAY 秒"
|
||||
|
||||
# 显示将要创建的项目列表
|
||||
log_info "将创建以下项目:"
|
||||
for i in $(seq "$START_NUM" "$END_NUM"); do
|
||||
PROJECT_ID="${PROJECT_ID_PREFIX}$(printf "%02d" "$i")"
|
||||
log_info " - $PROJECT_ID"
|
||||
done
|
||||
|
||||
# 确认开始创建
|
||||
log_warning "准备开始创建项目..."
|
||||
sleep 1
|
||||
|
||||
# 统计变量
|
||||
successful_projects=0
|
||||
failed_projects=0
|
||||
|
||||
# 创建项目和相关资源 - 主循环
|
||||
log_debug "开始主循环,从 $START_NUM 到 $END_NUM"
|
||||
|
||||
for i in $(seq "$START_NUM" "$END_NUM"); do
|
||||
PROJECT_ID="${PROJECT_ID_PREFIX}$(printf "%02d" "$i")"
|
||||
current_num=$((i - START_NUM + 1))
|
||||
|
||||
log_info "-----------------------------------------------------"
|
||||
log_info "开始处理项目 ($current_num/$REPEAT_NUM): $PROJECT_ID"
|
||||
log_debug "当前循环变量 i=$i, 项目ID=$PROJECT_ID"
|
||||
log_info "-----------------------------------------------------"
|
||||
|
||||
# 执行所有步骤
|
||||
project_success=true
|
||||
|
||||
log_debug "步骤1: 创建项目"
|
||||
if ! create_project "$PROJECT_ID" "$MAX_RETRIES" "$RETRY_DELAY"; then
|
||||
log_debug "创建项目失败"
|
||||
project_success=false
|
||||
fi
|
||||
|
||||
if [ "$project_success" = true ]; then
|
||||
log_debug "步骤2: 链接结算账号"
|
||||
if ! link_billing "$PROJECT_ID" "$billing_id" "$MAX_RETRIES" "$RETRY_DELAY"; then
|
||||
log_debug "链接结算账号失败"
|
||||
project_success=false
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$project_success" = true ]; then
|
||||
log_debug "步骤3: 启用服务"
|
||||
if ! enable_services "$PROJECT_ID" "$MAX_RETRIES" "$RETRY_DELAY"; then
|
||||
log_debug "启用服务失败"
|
||||
project_success=false
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$project_success" = true ]; then
|
||||
log_debug "步骤4: 创建服务账号"
|
||||
if ! create_service_account "$PROJECT_ID" "$MAX_RETRIES" "$RETRY_DELAY"; then
|
||||
log_debug "创建服务账号失败"
|
||||
project_success=false
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$project_success" = true ]; then
|
||||
log_debug "步骤5: 添加IAM策略"
|
||||
if ! add_iam_policy "$PROJECT_ID" "$MAX_RETRIES" "$RETRY_DELAY"; then
|
||||
log_debug "添加IAM策略失败"
|
||||
project_success=false
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$project_success" = true ]; then
|
||||
log_debug "步骤6: 创建服务账号密钥"
|
||||
if ! create_service_account_key "$PROJECT_ID" "$MAX_RETRIES" "$RETRY_DELAY"; then
|
||||
log_debug "创建服务账号密钥失败"
|
||||
project_success=false
|
||||
fi
|
||||
fi
|
||||
|
||||
# 统计结果
|
||||
if [ "$project_success" = true ]; then
|
||||
log_success "项目 $PROJECT_ID 所有操作处理完成"
|
||||
((successful_projects++))
|
||||
else
|
||||
log_warning "项目 $PROJECT_ID 处理过程中出现错误,部分操作可能未完成。"
|
||||
((failed_projects++))
|
||||
fi
|
||||
|
||||
# 添加延迟,避免 API 限制
|
||||
log_debug "等待2秒后继续下一个项目..."
|
||||
sleep 2
|
||||
|
||||
log_debug "完成项目 $PROJECT_ID 的处理,准备处理下一个项目"
|
||||
done
|
||||
|
||||
log_debug "主循环结束"
|
||||
|
||||
# 显示结果
|
||||
log_info "================== 执行完成 =================="
|
||||
log_success "成功创建: $successful_projects 个项目"
|
||||
log_error "创建失败: $failed_projects 个项目"
|
||||
log_info "详细日志: $LOG_FILE"
|
||||
echo "================== 创建完成于: $(date) ==================" >> "$LOG_FILE"
|
||||
|
||||
# 显示成功创建的项目列表
|
||||
if [ "$successful_projects" -gt 0 ]; then
|
||||
log_success "成功创建的项目:"
|
||||
for i in $(seq "$START_NUM" "$END_NUM"); do
|
||||
PROJECT_ID="${PROJECT_ID_PREFIX}$(printf "%02d" "$i")"
|
||||
if [ -f "pass-$PROJECT_ID.json" ]; then
|
||||
log_info " ✓ $PROJECT_ID (密钥文件: pass-$PROJECT_ID.json)"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$failed_projects" -gt 0 ]; then
|
||||
log_error "有 $failed_projects 个项目在处理过程中遇到无法恢复的错误。"
|
||||
log_info "请检查上面的日志以获取详细信息。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "所有项目创建/配置完成。"
|
||||
139
gcp/delete_all_projects.sh
Normal file
139
gcp/delete_all_projects.sh
Normal file
@@ -0,0 +1,139 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# 文件名: delete_all_projects.sh
|
||||
# 描述: 删除 GCP 账户下的所有项目(危险操作)
|
||||
# 作者: 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 LOG_FILE="deleted_projects_$(date +%Y%m%d_%H%M%S).log"
|
||||
log_set_file "$LOG_FILE"
|
||||
|
||||
#
|
||||
# 获取并展示所有项目
|
||||
#
|
||||
get_and_display_projects() {
|
||||
# 获取所有项目
|
||||
log_warning "获取项目列表..."
|
||||
local projects
|
||||
projects=$(gcloud projects list --format="value(projectId)")
|
||||
|
||||
if [ -z "$projects" ]; then
|
||||
log_warning "没有找到任何项目"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 显示所有项目
|
||||
log_warning "以下项目将被删除:"
|
||||
echo "$projects" | nl
|
||||
echo
|
||||
|
||||
# 统计项目数量
|
||||
local project_count
|
||||
project_count=$(echo "$projects" | wc -l)
|
||||
log_warning "总计: $project_count 个项目"
|
||||
|
||||
echo "$projects"
|
||||
}
|
||||
|
||||
#
|
||||
# 删除单个项目
|
||||
#
|
||||
delete_project() {
|
||||
local project_id=$1
|
||||
local log_file=$2
|
||||
|
||||
log_info "正在删除项目: $project_id"
|
||||
|
||||
if gcloud projects delete "$project_id" --quiet 2>&1 | tee -a "$log_file"; then
|
||||
log_success "成功删除项目: $project_id"
|
||||
echo "$(date): 成功删除项目 $project_id" >> "$log_file"
|
||||
return 0
|
||||
else
|
||||
log_error "删除项目 $project_id 失败"
|
||||
echo "$(date): 删除项目 $project_id 失败" >> "$log_file"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# 主函数 - 删除所有 GCP 项目
|
||||
#
|
||||
main() {
|
||||
# 第一次警告和确认
|
||||
log_error "################################################################################"
|
||||
log_error "警告:此脚本将删除您 GCP 账户下的所有项目!"
|
||||
log_error "这是一个不可逆的操作,所有项目数据将永久丢失!"
|
||||
log_error "################################################################################"
|
||||
echo
|
||||
|
||||
# 第一次确认
|
||||
read -p "您确定要继续吗?输入 'YES' 继续: " confirm1
|
||||
if [ "$confirm1" != "YES" ]; then
|
||||
log_warning "操作已取消"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 获取并展示项目列表
|
||||
local projects
|
||||
projects=$(get_and_display_projects)
|
||||
|
||||
# 统计项目数量
|
||||
local project_count
|
||||
project_count=$(echo "$projects" | wc -l)
|
||||
|
||||
# 第二次确认
|
||||
log_error "最后确认:您将要删除 $project_count 个项目!"
|
||||
read -p "输入 'DELETE ALL' 确认删除所有项目: " confirm2
|
||||
if [ "$confirm2" != "DELETE ALL" ]; then
|
||||
log_warning "操作已取消"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 开始删除项目
|
||||
log_warning "开始删除项目..."
|
||||
log_info "删除记录将保存到: $LOG_FILE"
|
||||
echo "================== 删除开始于: $(date) ==================" >> "$LOG_FILE"
|
||||
|
||||
local deleted_count=0
|
||||
local failed_count=0
|
||||
|
||||
for project_id in $projects; do
|
||||
if delete_project "$project_id" "$LOG_FILE"; then
|
||||
((deleted_count++))
|
||||
else
|
||||
((failed_count++))
|
||||
fi
|
||||
|
||||
# 添加短暂延迟,避免 API 限制
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# 显示结果
|
||||
log_info "================== 执行完成 ==================="
|
||||
log_success "成功删除: $deleted_count 个项目"
|
||||
log_error "删除失败: $failed_count 个项目"
|
||||
log_info "详细日志: $LOG_FILE"
|
||||
echo "================== 删除完成于: $(date) ==================" >> "$LOG_FILE"
|
||||
|
||||
# 如果有删除失败的项目,返回错误状态
|
||||
if [ "$failed_count" -gt 0 ]; then
|
||||
log_error "有 $failed_count 个项目删除失败。请检查日志。"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$@"
|
||||
142
linux/create_raid0_array.sh
Normal file
142
linux/create_raid0_array.sh
Normal file
@@ -0,0 +1,142 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# 文件名: create_raid0_array.sh
|
||||
# 描述: 创建并配置 RAID 0 存储阵列
|
||||
# 作者: 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 MOUNT_POINT="/mnt/raid0"
|
||||
|
||||
# 检查 root 权限
|
||||
check_root
|
||||
|
||||
#
|
||||
# 检查并安装依赖工具
|
||||
#
|
||||
# 参数:
|
||||
# $1 - 工具名称
|
||||
#
|
||||
check_and_install() {
|
||||
local tool=$1
|
||||
if ! command -v "$tool" &> /dev/null; then
|
||||
log_warning "$tool 未安装,正在尝试安装..."
|
||||
if command -v apt-get &> /dev/null; then
|
||||
run_command "安装 $tool" bash -c "apt-get update && apt-get install -y $tool"
|
||||
elif command -v yum &> /dev/null; then
|
||||
run_command "安装 $tool" yum install -y "$tool"
|
||||
else
|
||||
log_error "无法安装 $tool,请手动安装后重试"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# 主函数 - 创建 RAID 0 阵列
|
||||
#
|
||||
main() {
|
||||
log_info "============ RAID 0 创建程序 ============"
|
||||
|
||||
# 检查依赖
|
||||
check_and_install mdadm
|
||||
check_and_install lsblk
|
||||
log_success "所有依赖已满足"
|
||||
|
||||
# 获取系统盘
|
||||
local system_disk
|
||||
system_disk=$(mount | grep " / " | cut -d' ' -f1 | sed 's/[0-9]*//g')
|
||||
log_info "检测到系统盘: ${system_disk}"
|
||||
|
||||
# 获取可用磁盘
|
||||
local disks
|
||||
disks=$(lsblk -dno NAME | sed -e '/^loop/d' -e '/^sr/d' -e "/^${system_disk##*/}$/d" | sed 's/^/\/dev\//')
|
||||
|
||||
if [ -z "$disks" ]; then
|
||||
log_warning "没有找到除系统盘以外的硬盘"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 检查磁盘数量
|
||||
local disk_count
|
||||
disk_count=$(echo "$disks" | wc -l)
|
||||
if [ "$disk_count" -lt 2 ]; then
|
||||
log_error "至少需要两个磁盘来创建 RAID 0,只找到 $disk_count 个可用磁盘"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "找到以下磁盘可用于 RAID 0:"
|
||||
echo "$disks"
|
||||
|
||||
# 确认操作
|
||||
echo
|
||||
read -p "是否继续创建 RAID 0? 这将删除这些磁盘上的所有数据 (y/n): " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
log_warning "操作已取消"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 删除现有 RAID 配置
|
||||
mdadm --stop /dev/md0 2>/dev/null || true
|
||||
mdadm --zero-superblock $disks 2>/dev/null || true
|
||||
|
||||
# 创建 RAID 0 阵列
|
||||
log_info "正在创建 RAID 0 阵列..."
|
||||
run_command "创建 RAID" mdadm --create /dev/md0 --level=0 --raid-devices="$disk_count" $disks
|
||||
|
||||
# 检查状态
|
||||
if mdadm --detail /dev/md0 | grep -q 'State : clean'; then
|
||||
log_success "RAID 0 阵列创建成功"
|
||||
mdadm --detail /dev/md0
|
||||
else
|
||||
log_error "RAID 0 阵列创建失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 更新配置
|
||||
log_info "正在更新 mdadm.conf..."
|
||||
mdadm --detail --scan >> /etc/mdadm/mdadm.conf
|
||||
|
||||
# 创建文件系统
|
||||
log_info "正在创建 ext4 文件系统..."
|
||||
run_command "创建文件系统" mkfs.ext4 -F /dev/md0
|
||||
|
||||
# 创建挂载点
|
||||
mkdir -p "$MOUNT_POINT"
|
||||
|
||||
# 获取 UUID
|
||||
local raid_uuid
|
||||
raid_uuid=$(blkid -s UUID -o value /dev/md0)
|
||||
|
||||
# 更新 fstab
|
||||
log_info "正在更新 /etc/fstab..."
|
||||
echo "UUID=$raid_uuid $MOUNT_POINT ext4 defaults 0 0" >> /etc/fstab
|
||||
|
||||
# 挂载
|
||||
log_info "正在挂载 RAID 阵列..."
|
||||
if mount -a; then
|
||||
log_success "RAID 0 阵列已成功创建、格式化并挂载"
|
||||
log_success "挂载点: $MOUNT_POINT"
|
||||
else
|
||||
log_error "挂载 RAID 阵列失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_warning "注意: 建议重启系统以确保所有配置生效"
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$@"
|
||||
107
linux/install_oh_my_zsh.sh
Normal file
107
linux/install_oh_my_zsh.sh
Normal file
@@ -0,0 +1,107 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# 文件名: install_oh_my_zsh.sh
|
||||
# 描述: 安装和配置 Oh My Zsh 及常用插件
|
||||
# 作者: 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 INSTALL_DIR="${HOME}/.oh-my-zsh"
|
||||
readonly ZSH_CONFIG="${HOME}/.zshrc"
|
||||
readonly ZSH_CUSTOM="${INSTALL_DIR}/custom"
|
||||
|
||||
#
|
||||
# 检查并安装缺失的软件包
|
||||
#
|
||||
# 参数:
|
||||
# $@ - 要检查的软件包列表
|
||||
#
|
||||
check_and_install_packages() {
|
||||
local packages=("$@")
|
||||
local missing_packages=()
|
||||
|
||||
for package in "${packages[@]}"; do
|
||||
if ! check_command "$package"; then
|
||||
missing_packages+=("$package")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#missing_packages[@]} -gt 0 ]]; then
|
||||
log_warning "缺少以下软件包: ${missing_packages[*]}"
|
||||
if command -v apt-get &>/dev/null; then
|
||||
run_command "安装软件包" apt-get update
|
||||
run_command "安装软件包" apt-get install -y "${missing_packages[@]}"
|
||||
else
|
||||
log_error "只支持 apt-get 包管理器"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
log_success "所有软件包已安装"
|
||||
}
|
||||
|
||||
#
|
||||
# 安装 Oh My Zsh
|
||||
#
|
||||
install_zsh() {
|
||||
log_info "安装 zsh..."
|
||||
run_command "更新软件包" sudo apt update
|
||||
run_command "安装 zsh" sudo apt install zsh -y
|
||||
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
|
||||
}
|
||||
|
||||
#
|
||||
# 主函数
|
||||
#
|
||||
main() {
|
||||
log_info "============ Oh My Zsh 安装程序 ============"
|
||||
|
||||
check_and_install_packages "sudo" "git" "curl" "zsh"
|
||||
|
||||
if [ ! -d "$INSTALL_DIR" ]; then
|
||||
install_zsh
|
||||
else
|
||||
log_success "oh-my-zsh 已安装"
|
||||
fi
|
||||
|
||||
# 安装插件
|
||||
if [ ! -d "$ZSH_CUSTOM/plugins/zsh-autosuggestions" ]; then
|
||||
log_info "安装 zsh-autosuggestions..."
|
||||
run_command "克隆插件" git clone https://github.com/zsh-users/zsh-autosuggestions "$ZSH_CUSTOM/plugins/zsh-autosuggestions"
|
||||
fi
|
||||
|
||||
if [ ! -d "$ZSH_CUSTOM/plugins/zsh-syntax-highlighting" ]; then
|
||||
log_info "安装 zsh-syntax-highlighting..."
|
||||
run_command "克隆插件" git clone https://github.com/zsh-users/zsh-syntax-highlighting.git "$ZSH_CUSTOM/plugins/zsh-syntax-highlighting"
|
||||
fi
|
||||
|
||||
log_success "安装完成"
|
||||
|
||||
# 配置 .zshrc
|
||||
log_info "修改 .zshrc 文件..."
|
||||
cp "$ZSH_CONFIG" "$ZSH_CONFIG.bak"
|
||||
sed -i 's/plugins=(git)/plugins=(git zsh-autosuggestions zsh-syntax-highlighting extract)/' "$ZSH_CONFIG"
|
||||
sed -i 's/ZSH_THEME="robbyrussell"/ZSH_THEME="random"/' "$ZSH_CONFIG"
|
||||
log_success "配置完成"
|
||||
|
||||
# 设置默认 shell
|
||||
log_info "设置 zsh 为默认 shell..."
|
||||
chsh -s /bin/zsh
|
||||
log_success "设置完成"
|
||||
|
||||
log_info "重启 zsh..."
|
||||
zsh
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$@"
|
||||
94
linux/repartition_disks.sh
Normal file
94
linux/repartition_disks.sh
Normal file
@@ -0,0 +1,94 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# 文件名: repartition_disks.sh
|
||||
# 描述: 删除所有现有分区并创建新的 GPT 分区表(除了系统盘)
|
||||
# 作者: 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"
|
||||
|
||||
# 检查 root 权限
|
||||
check_root
|
||||
|
||||
#
|
||||
# 检查并安装依赖工具
|
||||
#
|
||||
# 参数:
|
||||
# $1 - 工具名称
|
||||
#
|
||||
check_and_install() {
|
||||
local tool=$1
|
||||
if ! command -v "$tool" &> /dev/null; then
|
||||
log_warning "$tool 未安装,正在尝试安装..."
|
||||
if command -v apt-get &> /dev/null; then
|
||||
run_command "安装 $tool" bash -c "apt-get update && apt-get install -y $tool"
|
||||
elif command -v yum &> /dev/null; then
|
||||
run_command "安装 $tool" yum install -y "$tool"
|
||||
else
|
||||
log_error "无法安装 $tool,请手动安装后重试"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# 主函数 - 重新分区所有非系统盘
|
||||
#
|
||||
main() {
|
||||
log_info "============ 磁盘重新分区程序 ============"
|
||||
|
||||
# 检查依赖
|
||||
check_and_install lsblk
|
||||
check_and_install parted
|
||||
check_and_install wipefs
|
||||
log_success "所有依赖已满足"
|
||||
|
||||
# 获取系统盘
|
||||
local system_disk
|
||||
system_disk=$(mount | grep " / " | cut -d' ' -f1 | sed 's/[0-9]*//g')
|
||||
log_info "检测到系统盘: ${system_disk}"
|
||||
|
||||
# 获取可用磁盘
|
||||
local disks
|
||||
disks=$(lsblk -dno NAME | sed -e '/^loop/d' -e '/^sr/d' -e "/^${system_disk##*/}$/d")
|
||||
|
||||
if [ -z "$disks" ]; then
|
||||
log_warning "没有找到除系统盘以外的硬盘"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 处理每个磁盘
|
||||
for disk in $disks; do
|
||||
log_info "正在处理硬盘: /dev/${disk}"
|
||||
|
||||
log_warning "删除 /dev/${disk} 上的所有分区..."
|
||||
run_command "清除分区签名" wipefs -a "/dev/${disk}"
|
||||
|
||||
log_warning "在 /dev/${disk} 上创建新的 GPT 分区表..."
|
||||
run_command "创建分区表" parted -s "/dev/${disk}" mklabel gpt
|
||||
|
||||
log_warning "在 /dev/${disk} 上创建新分区..."
|
||||
run_command "创建分区" parted -s "/dev/${disk}" mkpart primary 0% 100%
|
||||
|
||||
log_info "刷新分区表..."
|
||||
run_command "刷新分区" partprobe "/dev/${disk}"
|
||||
|
||||
log_success "完成处理 /dev/${disk}"
|
||||
echo
|
||||
done
|
||||
|
||||
log_success "所有操作完成"
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$@"
|
||||
389
oci/create_instance.sh
Normal file
389
oci/create_instance.sh
Normal 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 "$@"
|
||||
Reference in New Issue
Block a user