#!/usr/bin/env bash # ============================================================================= # Meta : 公共函数与变量库 # Version : 1.0.0 # Author : Bash Shell Senior Development Engineer # License : MIT # Description : 为备份脚本体系提供标准化的日志、远程执行、加解密及云存储管理功能。 # ============================================================================= #------------------------------------------------------------------------------ # 脚本严格模式 # -e: 命令失败时立即退出 # -u: 变量未定义时报错 # -o pipefail: 管道中任一命令失败则整个管道失败 #------------------------------------------------------------------------------ set -euo pipefail IFS=$'\n\t' #------------------------------------------------------------------------------ # 全局常量定义区 #------------------------------------------------------------------------------ # > 基础路径配置 readonly SCRIPT_RUN_DIR="/root/wdd/backup" readonly LOG_DIR="/root/wdd/backup/logs" # > 通用配置 readonly REMOTE_SSH_PORT="22333" readonly ENCRYPTION_PASSWORD_7ZIP="SuperWdd.CCC.123" # !!!请务必修改为强密码!!! readonly RCLONE_REMOTE_REPO="gd-zeaslity:CloneCone-BackUp" # rclone配置的远程仓库名及路径 # > 日志级别常量 readonly LOG_LEVEL_DEBUG=0 readonly LOG_LEVEL_INFO=1 readonly LOG_LEVEL_WARN=2 readonly LOG_LEVEL_ERROR=3 # > 默认日志级别 (可被调用脚本覆盖) CURRENT_LOG_LEVEL=${LOG_LEVEL_INFO} # > 颜色输出定义 readonly C_RED='\033[0;31m' readonly C_GREEN='\033[0;32m' readonly C_YELLOW='\033[1;33m' readonly C_BLUE='\033[0;34m' readonly C_NC='\033[0m' #------------------------------------------------------------------------------ # 模块依赖检查 #------------------------------------------------------------------------------ if ! command -v 7z &> /dev/null || ! command -v rclone &> /dev/null || ! command -v ssh &> /dev/null; then echo -e "${C_RED}[ERROR] Essential commands (7z, rclone, ssh) are not installed. Aborting.${C_NC}" >&2 exit 1 fi # ============================================================================= # 函数定义区 # ============================================================================= ### # 功能描述段: 记录标准化的分级日志 # @param level 日志级别 (DEBUG/INFO/WARN/ERROR) # @param message 要记录的日志消息 # @return <0> 成功 # @require LOG_DIR, CURRENT_LOG_LEVEL ### log_message() { local level="$1" local message="$2" local log_level_value local log_file log_file="${LOG_DIR}/backup_$(date +%Y%m%d).log" mkdir -p "${LOG_DIR}" case "${level}" in "DEBUG") log_level_value=${LOG_LEVEL_DEBUG} ;; "INFO") log_level_value=${LOG_LEVEL_INFO} ;; "WARN") log_level_value=${LOG_LEVEL_WARN} ;; "ERROR") log_level_value=${LOG_LEVEL_ERROR} ;; *) log_level_value=${LOG_LEVEL_INFO} ;; esac if [[ ${CURRENT_LOG_LEVEL} -le ${log_level_value} ]]; then local timestamp timestamp=$(date '+%Y-%m-%d %H:%M:%S') local color_prefix="${C_GREEN}" case "${level}" in "DEBUG") color_prefix="${C_BLUE}" ;; "INFO") color_prefix="${C_GREEN}" ;; "WARN") color_prefix="${C_YELLOW}" ;; "ERROR") color_prefix="${C_RED}" ;; esac # > 格式化日志条目 local log_entry log_entry=$(printf "[%-5s] %s: %s" "${level}" "${timestamp}" "${message}") # > 输出到标准输出/错误 echo -e "${color_prefix}${log_entry}${C_NC}" # > INFO及以上级别写入日志文件 if [[ ${log_level_value} -ge ${LOG_LEVEL_INFO} ]]; then echo "${log_entry}" >> "${log_file}" fi fi return 0 } ### # 功能描述段: 通过SSH在远程主机上安全地执行命令 # @param remote_user 远程主机用户名 # @param remote_host 远程主机名或IP地址 # @param remote_command 待执行的命令 # @param ssh_port SSH端口 (可选, 默认22333) # @return 远程命令的退出码 # @require REMOTE_SSH_PORT, ssh client ### execute_remote_command() { local remote_user="$1" local remote_host="$2" local remote_command="$3" local ssh_port=${4:-${REMOTE_SSH_PORT}} log_message "DEBUG" "Executing on [${remote_user}@${remote_host}:${ssh_port}]: ${remote_command}" ssh -p "${ssh_port}" "${remote_user}@${remote_host}" "${remote_command}" local exit_code=$? if [[ ${exit_code} -ne 0 ]]; then log_message "ERROR" "Remote command failed with exit code ${exit_code}." return ${exit_code} fi log_message "DEBUG" "Remote command executed successfully." return 0 } ### # 功能描述段: 使用7zip加密并压缩指定目录 # @param source_directory 需要压缩的源目录路径 # @param archive_path 生成的加密压缩包完整路径 # @return <0> 成功 | >0 失败 # @require ENCRYPTION_PASSWORD_7ZIP, 7z command ### encrypt_with_7zip() { local source_directory="$1" local archive_path="$2" if [[ ! -d "${source_directory}" ]]; then log_message "ERROR" "Source directory for encryption does not exist: ${source_directory}" return 1 fi log_message "INFO" "Encrypting '${source_directory}' to '${archive_path}'..." # > -mhe=on: 加密文件头, 防止泄露文件列表 # > -p: 指定密码 7z a -mhe=on -p"${ENCRYPTION_PASSWORD_7ZIP}" "${archive_path}" "${source_directory}"/* local exit_code=$? if [[ ${exit_code} -ne 0 ]]; then log_message "ERROR" "7zip encryption failed with exit code ${exit_code}." return ${exit_code} fi log_message "INFO" "Encryption completed successfully." return 0 } ### # 功能描述段: 使用rclone将本地文件复制到远程仓库 # @param source_file 本地源文件路径 # @param remote_destination rclone远程目标路径 (e.g., "google-drive:backup/app1/") # @return <0> 成功 | >0 失败 # @require rclone command ### rclone_copy() { local source_file="$1" local remote_destination="$2" if [[ ! -f "${source_file}" ]]; then log_message "ERROR" "Source file for rclone copy does not exist: ${source_file}" return 1 fi log_message "INFO" "Copying '${source_file}' to remote '${remote_destination}'..." rclone copy -P "${source_file}" "${remote_destination}" local exit_code=$? if [[ ${exit_code} -ne 0 ]]; then log_message "ERROR" "rclone copy failed with exit code ${exit_code}." return ${exit_code} fi log_message "INFO" "rclone copy completed successfully." return 0 } ### # 功能描述段: 控制rclone远程仓库中的副本数量,删除最旧的副本 # @param remote_path 远程仓库中的目录路径 # @param file_prefix 需要管理副本数量的文件名前缀 # @param max_replicas 允许保留的最大副本数量 # @return <0> 成功 | >0 失败 # @require rclone command ### rclone_control_replicas() { local remote_path="$1" local file_prefix="$2" local max_replicas="$3" log_message "INFO" "Checking replicas for '${file_prefix}*' in '${remote_path}'. Max allowed: ${max_replicas}." # > 获取远程文件列表及其修改时间 local remote_files remote_files=$(rclone lsf --format "tp" "${remote_path}" | grep "${file_prefix}" || true) if [[ -z "${remote_files}" ]]; then log_message "INFO" "No remote files found with prefix '${file_prefix}'. Nothing to do." return 0 fi local file_count file_count=$(echo "${remote_files}" | wc -l) if [[ ${file_count} -le ${max_replicas} ]]; then log_message "INFO" "Current replica count (${file_count}) is within the limit (${max_replicas})." return 0 fi local files_to_delete_count files_to_delete_count=$((file_count - max_replicas)) log_message "WARN" "Exceeding replica limit. Need to delete ${files_to_delete_count} oldest file(s)." # > 按时间排序并提取需要删除的文件名 local files_to_delete files_to_delete=$(echo "${remote_files}" | sort -k2 | head -n "${files_to_delete_count}" | awk -F';' '{print $1}') for file in ${files_to_delete}; do log_message "INFO" "Deleting oldest replica: ${file}" rclone deletefile "${remote_path}/${file}" if [[ $? -ne 0 ]]; then log_message "ERROR" "Failed to delete remote file: ${file}" # > 继续尝试删除其他文件,不立即失败 fi done log_message "INFO" "Replica control process finished." return 0 } ### # 功能描述段: 清理指定目录下的所有.7z加密压缩包 # @param target_directory 需要清理的目录路径 # @return <0> 成功 # @require find command ### cleanup_local_encrypted_files() { local target_directory="$1" log_message "INFO" "Cleaning up local encrypted files (*.7z) in '${target_directory}'..." find "${target_directory}" -maxdepth 1 -type f -name "*.7z" -delete log_message "INFO" "Local cleanup finished." return 0 }