Mysql备份并进行加密

备份需求#

每天凌晨把docker中的mysql库进行备份,并将备份后的文件加密压缩。

备份shell#

经过AI简单交互,得到一个shell如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#!/bin/bash

# MySQL备份脚本 - 企业级版(支持日志文件、压缩、加密和跳过指定表)
# 解密: openssl enc -d -aes-256-cbc -in /path/to/backup.sql.gz.enc -out /path/to/backup.sql.gz
# 再解压: gzip -d /path/to/backup.sql.gz
CONTAINER_NAME="mysql"
BACKUP_DIR="/path/to/backups"
LOG_FILE="${BACKUP_DIR}/backup.log"
USERNAME="用户"
PASSWORD="密码"
MAX_BACKUPS=30 # 保存备份数量
USE_SINGLE_TRANSACTION=1 # 1=使用事务备份(InnoDB) 0=跳过锁表
ENABLE_ENCRYPTION=1 # 1=启用加密 0=禁用加密
ENCRYPTION_ALGO="aes-256-cbc"
ENCRYPTION_PASSWORD="${PASSWORD}" # 目前和数据库密码一致,也可以修改。留空则交互式输入

# 要备份的数据库列表(空格分隔)
BACKUP_DATABASES="db1 db2"

# 要跳过的表列表(格式:数据库名.表名,空格分隔)
SKIP_TABLES=(
"db1.log_table"
"db2.large_table"
)

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m' # 无颜色

# 日志函数 - 同时输出到终端和日志文件
log_info() {
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $1"
echo -e "${GREEN}[INFO]${NC} $1"
echo "$msg" >> "$LOG_FILE"
}

log_warn() {
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] [WARN] $1"
echo -e "${YELLOW}[WARN]${NC} $1"
echo "$msg" >> "$LOG_FILE"
}

log_error() {
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $1"
echo -e "${RED}[ERROR]${NC} $1"
echo "$msg" >> "$LOG_FILE"
}

# 创建备份目录和日志文件
mkdir -p "$BACKUP_DIR" || { log_error "无法创建备份目录: $BACKUP_DIR"; exit 1; }
touch "$LOG_FILE" || { log_error "无法创建日志文件: $LOG_FILE"; exit 1; }

# 输出备份开始信息
log_info "====================================="
log_info "开始执行MySQL数据库备份脚本"
log_info "备份目录: $BACKUP_DIR"
log_info "日志文件: $LOG_FILE"
log_info "====================================="

# 检查依赖命令
for cmd in docker gzip openssl; do
command -v $cmd >/dev/null 2>&1 || { log_error "需要安装 $cmd 命令"; exit 1; }
done

# 检查容器是否运行
docker inspect -f '{{.State.Running}}' "$CONTAINER_NAME" 2>/dev/null | grep -q "true" || {
log_error "容器 $CONTAINER_NAME 未运行"
exit 1
}

# 验证数据库连接
docker exec "$CONTAINER_NAME" mysql -h 127.0.0.1 -u"$USERNAME" -p"$PASSWORD" -e "SELECT 1" >/dev/null 2>&1 || {
log_error "无法连接到MySQL,请检查用户名和密码"
exit 1
}

# 构建--ignore-table参数
build_ignore_tables() {
local db_name="$1"
local ignore_params=""

for table_spec in "${SKIP_TABLES[@]}"; do
db_part="${table_spec%%.*}"
table_part="${table_spec##*.}"

if [ "$db_part" = "$db_name" ]; then
ignore_params+=" --ignore-table=${db_name}.${table_part}"
fi
done

echo "$ignore_params"
}

# 加密函数
encrypt_file() {
local input_file="$1"
local output_file="${input_file}.enc"

log_info "正在加密: $input_file"

if [ -z "$ENCRYPTION_PASSWORD" ]; then
# 交互式输入密码
openssl enc -$ENCRYPTION_ALGO -salt -in "$input_file" -out "$output_file" 2>> "$LOG_FILE"
else
# 使用预设密码(不安全,建议仅用于自动化脚本)
openssl enc -$ENCRYPTION_ALGO -salt -pass "pass:$ENCRYPTION_PASSWORD" -in "$input_file" -out "$output_file" 2>> "$LOG_FILE"
fi

if [ $? -eq 0 ]; then
log_info "加密完成: $output_file"
rm -f "$input_file" # 删除未加密的文件
return 0
else
log_error "加密失败"
rm -f "$output_file" # 删除可能不完整的加密文件
return 1
fi
}

# 主备份流程
for DB_NAME in $BACKUP_DATABASES; do
BACKUP_FILE="${BACKUP_DIR}/${DB_NAME}_${DATE}.sql"
FINAL_FILE="$BACKUP_FILE"

log_info "开始备份数据库 '$DB_NAME'"

# 构建忽略表参数
IGNORE_PARAMS=$(build_ignore_tables "$DB_NAME")

# 打印跳过的表信息
# if [ -n "$IGNORE_PARAMS" ]; then
# log_info "将跳过以下表: $(echo "$IGNORE_PARAMS" | sed 's/ --ignore-table=/-/g' | sed 's/^-//')"
# fi

# 执行备份
if [ $USE_SINGLE_TRANSACTION -eq 1 ]; then
docker exec "$CONTAINER_NAME" mysqldump -h 127.0.0.1 -u"$USERNAME" -p"$PASSWORD" --single-transaction --databases "$DB_NAME" $IGNORE_PARAMS > "$BACKUP_FILE" 2>> "$LOG_FILE"
else
docker exec "$CONTAINER_NAME" mysqldump -h 127.0.0.1 -u"$USERNAME" -p"$PASSWORD" --skip-lock-tables --databases "$DB_NAME" $IGNORE_PARAMS > "$BACKUP_FILE" 2>> "$LOG_FILE"
fi

# 检查备份是否成功
if [ $? -ne 0 ]; then
log_error "备份数据库 '$DB_NAME' 失败"
rm -f "$BACKUP_FILE"
continue
fi

# 获取备份文件大小
FILE_SIZE=$(du -h "$BACKUP_FILE" | awk '{print $1}')
log_info "备份文件大小: $FILE_SIZE"

# 压缩文件
log_info "正在压缩备份文件"
gzip -9 "$BACKUP_FILE" 2>> "$LOG_FILE" || {
log_error "压缩失败"
rm -f "$BACKUP_FILE"
continue
}
BACKUP_FILE="${BACKUP_FILE}.gz"

# 获取压缩后文件大小
GZIP_SIZE=$(du -h "$BACKUP_FILE" | awk '{print $1}')
log_info "压缩后文件大小: $GZIP_SIZE"

# 加密文件
if [ $ENABLE_ENCRYPTION -eq 1 ]; then
encrypt_file "$BACKUP_FILE" || continue
FINAL_FILE="${BACKUP_FILE}.enc"

# 获取加密后文件大小
ENC_SIZE=$(du -h "$FINAL_FILE" | awk '{print $1}')
log_info "加密后文件大小: $ENC_SIZE"
else
FINAL_FILE="$BACKUP_FILE"
fi

log_info "数据库 '$DB_NAME' 备份完成: $FINAL_FILE"

# 清理旧备份
log_info "清理旧备份..."
if [ $ENABLE_ENCRYPTION -eq 1 ]; then
old_backups=$(ls -t "${BACKUP_DIR}/${DB_NAME}"*".sql.gz.enc" 2>/dev/null | tail -n +$((MAX_BACKUPS + 1)))
else
old_backups=$(ls -t "${BACKUP_DIR}/${DB_NAME}"*".sql.gz" 2>/dev/null | tail -n +$((MAX_BACKUPS + 1)))
fi

if [ -n "$old_backups" ]; then
echo "$old_backups" | xargs -r rm -f
log_info "已清理 $(echo "$old_backups" | wc -l) 个旧备份文件"
else
log_info "没有需要清理的旧备份文件"
fi
done

# 输出备份完成信息
log_info "====================================="
log_info "所有数据库备份完成!"
log_info "日志文件位置: $LOG_FILE"
log_info "====================================="

恢复命令#

配置好crontab就可以用了,后续解密、解压如下:

1
2
3
4
5
6
7
8
# 1. 解密,交互式输入密码
openssl enc -d -aes-256-cbc -in /path/to/backup.sql.gz.enc -out /path/to/backup.sql.gz

# 2. 解压SQL文件
gzip -d backup.sql.gz

# 3. 恢复到MySQL(假设容器已运行)
docker exec -i CONTAINER_NAME mysql -u USER_NAME -pPASSWORD DBNAME < backup.sql