Files
shell-scripts/0-部署应用/CloudCone-备份中心/common.sh
2025-09-03 14:14:19 +08:00

272 lines
9.0 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.

#!/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 <string> 日志级别 (DEBUG/INFO/WARN/ERROR)
# @param message <string> 要记录的日志消息
# @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 <string> 远程主机用户名
# @param remote_host <string> 远程主机名或IP地址
# @param remote_command <string> 待执行的命令
# @param ssh_port <string> SSH端口 (可选, 默认22333)
# @return <exit_code> 远程命令的退出码
# @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 <string> 需要压缩的源目录路径
# @param archive_path <string> 生成的加密压缩包完整路径
# @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 <string> 本地源文件路径
# @param remote_destination <string> 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 <string> 远程仓库中的目录路径
# @param file_prefix <string> 需要管理副本数量的文件名前缀
# @param max_replicas <integer> 允许保留的最大副本数量
# @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 <string> 需要清理的目录路径
# @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
}