343 lines
8.8 KiB
Bash
343 lines
8.8 KiB
Bash
#!/bin/bash
|
||
# =============================================================================
|
||
# b-vault-warden备份脚本
|
||
# 功能:远程执行Vaultwarden备份、同步备份文件、加密压缩及清理
|
||
# 版本:1.0.0
|
||
# 作者:Shell脚本工程师
|
||
# 许可证:MIT License
|
||
# 依赖:ssh, rsync, 7zip, docker (远程主机)
|
||
# =============================================================================
|
||
|
||
set -euo pipefail
|
||
IFS=$'\n\t'
|
||
|
||
# > 全局常量定义
|
||
readonly SCRIPT_NAME="$(basename "$0")"
|
||
readonly SCRIPT_DIR="/root/wdd/backup"
|
||
readonly LOCK_FILE="/root/wdd/backup/${SCRIPT_NAME}.lock"
|
||
|
||
# > 配置参数(可根据需要调整为环境变量)
|
||
readonly REMOTE_HOST="s5"
|
||
readonly REMOTE_PORT="22333"
|
||
readonly REMOTE_USER="root"
|
||
readonly REMOTE_BACKUP_CMD="docker exec vault-warden /vaultwarden backup"
|
||
readonly REMOTE_DATA_DIR="/data/vault-warden/persist-data"
|
||
readonly LOCAL_BACKUP_DIR="/data/s5_146-56-159-175/vault_warden"
|
||
readonly BACKUP_PATTERNS=(
|
||
"config.json"
|
||
"rsa_key*"
|
||
"attachments"
|
||
"icon_cache"
|
||
"sends"
|
||
"db_*.sqlite3"
|
||
)
|
||
readonly ENCRYPTION_PASSWORD="SuperWdd.123" # > 请在实际使用时修改
|
||
|
||
# > 日志配置
|
||
readonly LOG_DIR="${SCRIPT_DIR}/logs"
|
||
readonly LOG_FILE="${LOG_DIR}/vault_warden_backup_$(date +%Y%m%d).log"
|
||
|
||
# > 颜色输出定义
|
||
readonly RED='\033[0;31m'
|
||
readonly GREEN='\033[0;32m'
|
||
readonly YELLOW='\033[1;33m'
|
||
readonly BLUE='\033[0;34m'
|
||
readonly NC='\033[0m'
|
||
|
||
# =============================================================================
|
||
# 日志函数集
|
||
# =============================================================================
|
||
|
||
###
|
||
# 初始化日志系统
|
||
# @require 无
|
||
# @return 0 成功 | >0 失败
|
||
###
|
||
init_log_system() {
|
||
mkdir -p "${LOG_DIR}" || return 1
|
||
touch "${LOG_FILE}" || return 1
|
||
return 0
|
||
}
|
||
|
||
###
|
||
# 记录日志消息
|
||
# @param level string 日志级别(DEBUG/INFO/WARN/ERROR)
|
||
# @param message string 日志消息
|
||
# @require LOG_FILE
|
||
# @return 0 成功
|
||
###
|
||
log_message() {
|
||
local level="$1"
|
||
local message="$2"
|
||
local timestamp
|
||
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||
|
||
case "${level}" in
|
||
"DEBUG") echo -e "${BLUE}[DEBUG]${NC} ${timestamp} - ${message}" | tee -a "${LOG_FILE}" ;;
|
||
"INFO") echo -e "${GREEN}[INFO]${NC} ${timestamp} - ${message}" | tee -a "${LOG_FILE}" ;;
|
||
"WARN") echo -e "${YELLOW}[WARN]${NC} ${timestamp} - ${message}" | tee -a "${LOG_FILE}" >&2 ;;
|
||
"ERROR") echo -e "${RED}[ERROR]${NC} ${timestamp} - ${message}" | tee -a "${LOG_FILE}" >&2 ;;
|
||
*) echo "${timestamp} - ${message}" | tee -a "${LOG_FILE}" ;;
|
||
esac
|
||
return 0
|
||
}
|
||
|
||
# =============================================================================
|
||
# 工具函数集
|
||
# =============================================================================
|
||
|
||
###
|
||
# 检查命令是否存在
|
||
# @param command_name string 命令名称
|
||
# @require 无
|
||
# @return 0 存在 | 1 不存在
|
||
###
|
||
check_command() {
|
||
local command_name="$1"
|
||
if ! command -v "${command_name}" >/dev/null 2>&1; then
|
||
log_message "ERROR" "命令不存在: ${command_name}"
|
||
return 1
|
||
fi
|
||
return 0
|
||
}
|
||
|
||
###
|
||
# 执行远程SSH命令
|
||
# @param command string 要执行的命令
|
||
# @require REMOTE_HOST, REMOTE_PORT, REMOTE_USER
|
||
# @return 远程命令的退出码
|
||
###
|
||
execute_remote_command() {
|
||
local command="$1"
|
||
ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" "${command}"
|
||
return $?
|
||
}
|
||
|
||
###
|
||
# 创建锁文件防止并发执行
|
||
# @require LOCK_FILE
|
||
# @return 0 成功获取锁 | 1 锁已存在
|
||
###
|
||
acquire_lock() {
|
||
if [ -e "${LOCK_FILE}" ]; then
|
||
log_message "ERROR" "备份任务正在运行或异常退出,请检查锁文件: ${LOCK_FILE}"
|
||
return 1
|
||
fi
|
||
echo "$$" > "${LOCK_FILE}"
|
||
trap 'release_lock' EXIT
|
||
return 0
|
||
}
|
||
|
||
###
|
||
# 释放锁文件
|
||
# @require LOCK_FILE
|
||
# @return 0 成功
|
||
###
|
||
release_lock() {
|
||
[ -e "${LOCK_FILE}" ] && rm -f "${LOCK_FILE}"
|
||
return 0
|
||
}
|
||
|
||
# =============================================================================
|
||
# 核心备份函数
|
||
# =============================================================================
|
||
|
||
###
|
||
# 远程执行Vaultwarden备份命令
|
||
# @require execute_remote_command, REMOTE_BACKUP_CMD
|
||
# @return 0 成功 | >0 失败
|
||
###
|
||
remote_execute_backup() {
|
||
log_message "INFO" "开始在远程主机执行Vaultwarden备份..."
|
||
|
||
if ! execute_remote_command "${REMOTE_BACKUP_CMD}"; then
|
||
log_message "ERROR" "远程备份命令执行失败"
|
||
return 1
|
||
fi
|
||
|
||
log_message "INFO" "远程备份命令执行成功"
|
||
return 0
|
||
}
|
||
|
||
###
|
||
# 使用rsync同步备份文件到本地
|
||
# @require REMOTE_HOST, REMOTE_PORT, REMOTE_USER, REMOTE_DATA_DIR, LOCAL_BACKUP_DIR, BACKUP_PATTERNS
|
||
# @return 0 成功 | >0 失败
|
||
###
|
||
sync_backup_files() {
|
||
log_message "INFO" "开始同步备份文件到本地..."
|
||
|
||
# > 创建本地暂存目录
|
||
mkdir -p "${LOCAL_BACKUP_DIR}" || {
|
||
log_message "ERROR" "创建本地暂存目录失败: ${LOCAL_BACKUP_DIR}"
|
||
return 1
|
||
}
|
||
|
||
# > 构建rsync命令
|
||
local rsync_cmd="rsync -avz --progress -e 'ssh -p ${REMOTE_PORT}'"
|
||
|
||
for pattern in "${BACKUP_PATTERNS[@]}"; do
|
||
rsync_cmd+=" ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DATA_DIR}/${pattern}"
|
||
done
|
||
|
||
rsync_cmd+=" ${LOCAL_BACKUP_DIR}/"
|
||
|
||
# > 执行rsync同步
|
||
if ! eval "${rsync_cmd}"; then
|
||
log_message "ERROR" "文件同步失败"
|
||
return 1
|
||
fi
|
||
|
||
log_message "INFO" "文件同步完成"
|
||
return 0
|
||
}
|
||
|
||
###
|
||
# 使用7zip加密压缩备份文件
|
||
# @require LOCAL_BACKUP_DIR, LOCAL_BACKUP_DIR, ENCRYPTION_PASSWORD
|
||
# @return 0 成功 | >0 失败
|
||
###
|
||
encrypt_and_compress() {
|
||
log_message "INFO" "开始加密压缩备份文件..."
|
||
|
||
# > 检查7zip命令
|
||
if ! check_command "7z"; then
|
||
log_message "ERROR" "7zip命令不存在,请安装p7zip-full包"
|
||
return 1
|
||
fi
|
||
|
||
# > 创建备份目录
|
||
mkdir -p "${LOCAL_BACKUP_DIR}" || {
|
||
log_message "ERROR" "创建备份目录失败: ${LOCAL_BACKUP_DIR}"
|
||
return 1
|
||
}
|
||
|
||
local backup_file="${LOCAL_BACKUP_DIR}/vaultwarden-backup-$(date +%Y%m%d-%H%M%S).7z"
|
||
|
||
# > 执行加密压缩
|
||
if ! (cd "${LOCAL_BACKUP_DIR}" && 7z a -p"${ENCRYPTION_PASSWORD}" -mhe=on "${backup_file}" . >/dev/null); then
|
||
log_message "ERROR" "加密压缩失败"
|
||
return 1
|
||
fi
|
||
|
||
log_message "INFO" "加密压缩完成: ${backup_file}"
|
||
return 0
|
||
}
|
||
|
||
###
|
||
# 远程删除备份数据库文件
|
||
# @require execute_remote_command, REMOTE_DATA_DIR
|
||
# @return 0 成功 | >0 失败
|
||
###
|
||
remote_cleanup_backup() {
|
||
log_message "INFO" "开始清理远程备份文件..."
|
||
|
||
local cleanup_cmd="rm -rf ${REMOTE_DATA_DIR}/db_*.sqlite3"
|
||
|
||
if ! execute_remote_command "${cleanup_cmd}"; then
|
||
log_message "ERROR" "远程清理失败"
|
||
return 1
|
||
fi
|
||
|
||
log_message "INFO" "远程清理完成"
|
||
return 0
|
||
}
|
||
|
||
###
|
||
# 清理本地暂存目录
|
||
# @require LOCAL_BACKUP_DIR
|
||
# @return 0 成功
|
||
###
|
||
local_cleanup() {
|
||
log_message "INFO" "清理本地暂存目录..."
|
||
[ -d "${LOCAL_BACKUP_DIR}" ] && rm -rf "${LOCAL_BACKUP_DIR}"
|
||
return 0
|
||
}
|
||
|
||
# =============================================================================
|
||
# 主执行流程
|
||
# =============================================================================
|
||
|
||
###
|
||
# 主备份流程
|
||
# @require 所有上述函数
|
||
# @return 0 成功 | >0 失败
|
||
###
|
||
main_backup_process() {
|
||
log_message "INFO" "=== 开始Vaultwarden备份任务 ==="
|
||
|
||
# > 检查依赖命令
|
||
local required_commands=("ssh" "rsync" "7z")
|
||
for cmd in "${required_commands[@]}"; do
|
||
if ! check_command "${cmd}"; then
|
||
return 1
|
||
fi
|
||
done
|
||
|
||
# > 执行备份流程
|
||
local steps=(
|
||
remote_execute_backup
|
||
sync_backup_files
|
||
encrypt_and_compress
|
||
remote_cleanup_backup
|
||
# local_cleanup
|
||
)
|
||
|
||
for step in "${steps[@]}"; do
|
||
if ! "${step}"; then
|
||
log_message "ERROR" "备份任务在第 ${#steps[@]} 步失败"
|
||
return 1
|
||
fi
|
||
done
|
||
|
||
log_message "INFO" "=== Vaultwarden备份任务完成 ==="
|
||
return 0
|
||
}
|
||
|
||
# =============================================================================
|
||
# 脚本入口点
|
||
# =============================================================================
|
||
|
||
# > 设置错误处理
|
||
trap 'log_message "ERROR" "脚本异常退出"; release_lock; exit 1' ERR
|
||
|
||
# > 主执行块
|
||
main() {
|
||
if ! acquire_lock; then
|
||
exit 1
|
||
fi
|
||
|
||
if ! init_log_system; then
|
||
log_message "ERROR" "日志系统初始化失败"
|
||
exit 1
|
||
fi
|
||
|
||
if ! main_backup_process; then
|
||
log_message "ERROR" "备份任务执行失败"
|
||
exit 1
|
||
fi
|
||
|
||
release_lock
|
||
exit 0
|
||
}
|
||
|
||
# > 脚本执行入口
|
||
main "$@"
|
||
|
||
# =============================================================================
|
||
# 函数调用关系图
|
||
# =============================================================================
|
||
# main
|
||
# ├── acquire_lock
|
||
# ├── init_log_system
|
||
# └── main_backup_process
|
||
# ├── check_command (多次调用)
|
||
# ├── remote_execute_backup
|
||
# │ └── execute_remote_command
|
||
# ├── sync_backup_files
|
||
# ├── encrypt_and_compress
|
||
# │ └── check_command
|
||
# ├── remote_cleanup_backup
|
||
# │ └── execute_remote_command
|
||
# └── local_cleanup
|