272 lines
9.0 KiB
Bash
272 lines
9.0 KiB
Bash
#!/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
|
||
}
|