Files
s-hy2/scripts/common.sh
T
sindricn 8ae2bbf5d1 修复
2025-10-01 22:21:42 +08:00

535 lines
13 KiB
Bash
Raw 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
# 公共函数库 - 统一错误处理和日志记录
# 为所有脚本提供标准化的错误处理、日志记录和工具函数
# 适度的错误处理 (不使用 -e 避免意外退出)
set -uo pipefail
# 全局变量 (防止重复定义)
if [[ -z "${SCRIPT_NAME:-}" ]]; then
readonly SCRIPT_NAME="${0##*/}"
fi
if [[ -z "${LOG_DIR:-}" ]]; then
LOG_DIR="/var/log/s-hy2"
fi
if [[ -z "${LOG_FILE:-}" ]]; then
LOG_FILE="$LOG_DIR/s-hy2.log"
fi
if [[ -z "${PID_FILE:-}" ]]; then
readonly PID_FILE="/var/run/s-hy2.pid"
fi
# 颜色定义 (防止与主脚本冲突)
if [[ -z "${RED:-}" ]]; then
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly CYAN='\033[0;36m'
readonly NC='\033[0m'
fi
# 日志级别 (防止重复定义)
if [[ -z "${LOG_LEVEL_DEBUG:-}" ]]; then
readonly LOG_LEVEL_DEBUG=0
readonly LOG_LEVEL_INFO=1
readonly LOG_LEVEL_WARN=2
readonly LOG_LEVEL_ERROR=3
readonly LOG_LEVEL_FATAL=4
fi
# 当前日志级别 (默认为 INFO)
LOG_LEVEL=${LOG_LEVEL:-$LOG_LEVEL_INFO}
# 初始化日志系统 - 智能降级版本
init_logging() {
# 尝试创建系统日志目录
if [[ $EUID -eq 0 ]] && [[ ! -d "$LOG_DIR" ]]; then
if mkdir -p "$LOG_DIR" 2>/dev/null; then
# 成功创建系统日志目录
:
else
# Root用户但无法创建系统目录,降级到/tmp
LOG_DIR="/tmp/s-hy2"
LOG_FILE="$LOG_DIR/s-hy2.log"
mkdir -p "$LOG_DIR" 2>/dev/null || true
fi
elif [[ $EUID -ne 0 ]]; then
# 非Root用户,使用用户目录
local user_log_dir="${HOME}/.cache/s-hy2"
if mkdir -p "$user_log_dir" 2>/dev/null; then
LOG_DIR="$user_log_dir"
LOG_FILE="$LOG_DIR/s-hy2.log"
else
# 降级到临时目录
LOG_DIR="/tmp/s-hy2-$(whoami)"
LOG_FILE="$LOG_DIR/s-hy2.log"
mkdir -p "$LOG_DIR" 2>/dev/null || true
fi
fi
# 设置日志文件权限
if [[ -f "$LOG_FILE" ]]; then
chmod 644 "$LOG_FILE" 2>/dev/null || true
else
touch "$LOG_FILE" 2>/dev/null && chmod 644 "$LOG_FILE" 2>/dev/null || true
fi
}
# 统一的日志记录函数
log_message() {
local level="$1"
local level_num="$2"
local color="$3"
shift 3
local message="$*"
# 检查日志级别
if [[ $level_num -lt $LOG_LEVEL ]]; then
return 0
fi
local timestamp
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
# 控制台输出(带颜色)
echo -e "${color}[${level}]${NC} $message" >&2
# 文件输出(无颜色)
if [[ -w "$LOG_FILE" ]] 2>/dev/null; then
echo "[$timestamp] [$SCRIPT_NAME:$$] [$level] $message" >> "$LOG_FILE"
fi
}
# 各级别日志函数
log_debug() {
log_message "DEBUG" $LOG_LEVEL_DEBUG "$CYAN" "$@"
}
log_info() {
log_message "INFO" $LOG_LEVEL_INFO "$BLUE" "$@"
}
log_warn() {
log_message "WARN" $LOG_LEVEL_WARN "$YELLOW" "$@"
}
log_error() {
log_message "ERROR" $LOG_LEVEL_ERROR "$RED" "$@"
}
log_fatal() {
log_message "FATAL" $LOG_LEVEL_FATAL "$RED" "$@"
}
log_success() {
log_message "SUCCESS" $LOG_LEVEL_INFO "$GREEN" "$@"
}
# 错误处理函数
error_exit() {
local message="$1"
local exit_code="${2:-1}"
log_error "$message"
# 执行清理
if command -v cleanup >/dev/null 2>&1; then
cleanup "$exit_code"
fi
exit "$exit_code"
}
# 设置错误陷阱
setup_error_handling() {
# 捕获各种信号
trap 'handle_error $? $LINENO' ERR
trap 'handle_signal INT "中断信号"' INT
trap 'handle_signal TERM "终止信号"' TERM
trap 'handle_signal HUP "挂起信号"' HUP
}
# 错误处理器
handle_error() {
local exit_code=$1
local line_number=$2
log_error "脚本执行失败: 退出码 $exit_code, 行号 $line_number"
# 显示调用栈
local i=0
while [[ ${FUNCNAME[$i]} ]]; do
log_debug "调用栈 $i: ${FUNCNAME[$i]} (${BASH_SOURCE[$i]}:${BASH_LINENO[$i]})"
((i++))
done
# 执行清理
if command -v cleanup >/dev/null 2>&1; then
cleanup "$exit_code"
fi
exit "$exit_code"
}
# 信号处理器
handle_signal() {
local signal="$1"
local description="$2"
log_info "接收到 $description ($signal), 正在清理..."
# 执行清理
if command -v cleanup >/dev/null 2>&1; then
cleanup 130
fi
exit 130
}
# 默认清理函数
cleanup() {
local exit_code="${1:-0}"
log_debug "执行清理操作 (退出码: $exit_code)"
# 清理临时文件
if [[ -n "${TEMP_FILES:-}" ]]; then
for temp_file in $TEMP_FILES; do
if [[ -f "$temp_file" ]]; then
rm -f "$temp_file"
log_debug "删除临时文件: $temp_file"
fi
done
fi
# 清理临时目录
if [[ -n "${TEMP_DIRS:-}" ]]; then
for temp_dir in $TEMP_DIRS; do
if [[ -d "$temp_dir" ]]; then
rm -rf "$temp_dir"
log_debug "删除临时目录: $temp_dir"
fi
done
fi
# 清理进程文件
if [[ -f "$PID_FILE" ]]; then
rm -f "$PID_FILE"
fi
}
# 创建安全临时文件
create_temp_file() {
local temp_file
temp_file=$(mktemp)
chmod 600 "$temp_file"
# 添加到清理列表
TEMP_FILES="${TEMP_FILES:-} $temp_file"
echo "$temp_file"
}
# 创建安全临时目录
create_temp_dir() {
local temp_dir
temp_dir=$(mktemp -d)
chmod 700 "$temp_dir"
# 添加到清理列表
TEMP_DIRS="${TEMP_DIRS:-} $temp_dir"
echo "$temp_dir"
}
# 检查命令是否存在
require_command() {
local cmd="$1"
local package="${2:-$cmd}"
if ! command -v "$cmd" >/dev/null 2>&1; then
error_exit "缺少必要的命令: $cmd (请安装 $package)"
fi
}
# 检查文件是否可读
require_file() {
local file="$1"
if [[ ! -f "$file" ]]; then
error_exit "文件不存在: $file"
fi
if [[ ! -r "$file" ]]; then
error_exit "文件不可读: $file"
fi
}
# 检查目录是否可写
require_writable_dir() {
local dir="$1"
if [[ ! -d "$dir" ]]; then
error_exit "目录不存在: $dir"
fi
if [[ ! -w "$dir" ]]; then
error_exit "目录不可写: $dir"
fi
}
# 检查root权限
require_root() {
if [[ $EUID -ne 0 ]]; then
error_exit "此脚本需要root权限运行"
fi
}
# 标准化错误处理函数
setup_error_handling() {
# 统一的错误处理设置
set -uo pipefail
# 捕获ERR信号并调用错误处理函数
trap 'handle_script_error $? $LINENO "$BASH_COMMAND" "${FUNCNAME[*]:-main}"' ERR
}
# 错误处理函数
handle_script_error() {
local exit_code=$1
local line_number=$2
local command=$3
local function_stack=$4
log_error "脚本执行错误:"
log_error " 文件: ${BASH_SOURCE[1]:-$0}"
log_error " 行号: $line_number"
log_error " 命令: $command"
log_error " 函数栈: $function_stack"
log_error " 退出码: $exit_code"
# 在某些情况下不退出,让调用者处理
if [[ "${CONTINUE_ON_ERROR:-false}" == "true" ]]; then
return $exit_code
fi
exit $exit_code
}
# 安全执行函数 - 用于可能失败的操作
safe_execute() {
local command_description="$1"
shift
log_info "执行: $command_description"
if "$@"; then
log_success "$command_description - 成功"
return 0
else
local exit_code=$?
log_error "$command_description - 失败 (退出码: $exit_code)"
return $exit_code
fi
}
# 标准化脚本初始化函数
init_script() {
local script_description="${1:-Shell脚本}"
# 设置错误处理
setup_error_handling
# 初始化日志系统
init_logging
log_debug "开始执行: $script_description"
log_debug "脚本路径: ${BASH_SOURCE[1]:-$0}"
log_debug "执行用户: $(whoami)"
log_debug "工作目录: $(pwd)"
}
# 等待用户确认
wait_for_user() {
echo ""
read -p "按回车键继续..." -r
}
# 检查Hysteria2是否已安装和配置
check_hysteria2_ready() {
local check_type="${1:-install}" # install, config, service
case $check_type in
"install")
if ! command -v hysteria >/dev/null 2>&1; then
log_warn "Hysteria2 未安装"
echo ""
echo -e "${YELLOW}提示:${NC}请先安装 Hysteria2"
echo " 返回主菜单选择 '1. 安装 Hysteria2'"
echo ""
wait_for_user
return 1
fi
;;
"config")
if [[ ! -f "/etc/hysteria/config.yaml" ]]; then
log_warn "Hysteria2 配置文件不存在"
echo ""
echo -e "${YELLOW}提示:${NC}请先配置 Hysteria2"
echo " 1. 返回主菜单选择 '2. 快速配置' 或 '3. 手动配置'"
echo " 2. 如未安装,请先选择 '1. 安装 Hysteria2'"
echo ""
wait_for_user
return 1
fi
;;
"service")
if ! systemctl is-enabled hysteria-server >/dev/null 2>&1; then
log_warn "Hysteria2 服务未启用"
echo ""
echo -e "${YELLOW}提示:${NC}请先启用服务"
echo " 返回主菜单选择 '7. 服务管理'"
echo ""
wait_for_user
return 1
fi
;;
esac
return 0
}
# 显示进度条
show_progress() {
local current="$1"
local total="$2"
local width=50
local percentage=$((current * 100 / total))
local completed=$((current * width / total))
printf "\r["
printf "%${completed}s" | tr ' ' '='
printf "%$((width - completed))s" | tr ' ' '-'
printf "] %d%% (%d/%d)" "$percentage" "$current" "$total"
}
# 并发控制
init_semaphore() {
local max_jobs="$1"
local semaphore_pipe="/tmp/semaphore.$$"
mkfifo "$semaphore_pipe"
exec 3<>"$semaphore_pipe"
for ((i=0; i<max_jobs; i++)); do
echo "token" >&3
done
# 添加到清理列表
TEMP_FILES="${TEMP_FILES:-} $semaphore_pipe"
}
acquire_semaphore() {
read -u 3
}
release_semaphore() {
echo "token" >&3
}
# 网络连接检查
check_internet_connection() {
local test_urls=(
"https://www.google.com"
"https://github.com"
"https://www.cloudflare.com"
)
for url in "${test_urls[@]}"; do
if curl -s --connect-timeout 5 --max-time 10 "$url" >/dev/null 2>&1; then
return 0
fi
done
return 1
}
# 导出函数供其他脚本使用
export -f init_logging
export -f log_debug log_info log_warn log_error log_fatal log_success
export -f error_exit setup_error_handling cleanup
export -f create_temp_file create_temp_dir
export -f require_command require_file require_writable_dir require_root
export -f wait_for_user show_progress
export -f init_semaphore acquire_semaphore release_semaphore
export -f check_internet_connection
# ===== 用户友好提示函数 =====
# 标准化的操作提示
show_operation_result() {
local operation="$1"
local status="$2" # success, error, warning, info
local message="$3"
case $status in
"success")
echo -e "${GREEN}$operation 成功${NC}: $message"
;;
"error")
echo -e "${RED}$operation 失败${NC}: $message"
;;
"warning")
echo -e "${YELLOW}⚠️ $operation 警告${NC}: $message"
;;
"info")
echo -e "${BLUE}$operation 信息${NC}: $message"
;;
*)
echo "$operation: $message"
;;
esac
}
# 标准化的冲突提示
show_conflict_prompt() {
local item_type="$1"
local existing_item="$2"
local new_item="$3"
echo ""
echo -e "${YELLOW}⚠️ 冲突检测 ⚠️${NC}"
echo -e "${YELLOW}检测到现有的 ${item_type}: ${CYAN}$existing_item${NC}"
echo -e "${YELLOW}正在尝试添加: ${CYAN}$new_item${NC}"
echo ""
echo -e "${BLUE}选择操作:${NC}"
echo -e "${GREEN}1.${NC} 继续并覆盖现有项"
echo -e "${RED}2.${NC} 取消操作"
echo ""
}
# 标准化的确认提示
show_confirmation_prompt() {
local action="$1"
local target="$2"
echo ""
echo -e "${YELLOW}⚠️ 确认操作 ⚠️${NC}"
echo -e "${YELLOW}即将执行: ${CYAN}$action${NC}"
echo -e "${YELLOW}目标: ${CYAN}$target${NC}"
echo ""
read -p "确认执行此操作? [y/N]: " confirm
[[ $confirm =~ ^[Yy]$ ]]
}
export -f show_operation_result show_conflict_prompt show_confirmation_prompt
# 自动初始化(仅初始化日志,不设置错误陷阱)
if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then
# 被作为模块导入时自动初始化日志
init_logging
# 不自动调用 setup_error_handling
# 让各个脚本根据需要自己调用
fi