印度孟买某公司开发的 Virtualizor 面板,让不少 VPS 服务商都栽了跟头。从去年的 ColorCrossing 到最近的 CloudCone,黑客利用漏洞入侵服务器并进行勒索,导致大量用户数据全灭。
面对此类「黑天鹅事件」,谁也没法独善其身,是时候完善一下我的数据自动化备份方案了。
选择备份工具#
几年前我一直使用 Duplicacy CLI,甚至还写过两篇又臭又长的教程。但它的开源许可证不够清晰,且已停更近一年,我开始寻找更好的替代品。我找到了:
最终的选择是 Kopia ,它支持增量快照、自带端到端 加密、且上手极其简单。
选择存储服务#
Kopia 支持几乎所有主流的存储服务和协议:
- S3 及 S3 兼容存储
- 本机或网络附加存储
- Azure Blob 存储
- Backblaze B2 存储
- Google Cloud 存储
- Google 云端硬盘
- WebDAV/SFTP/Rclone
- ……
对象存储我选择了 Backblaze B2 , 按量计费计划 仅需 41.21 元/TB。A 类操作免费且每天赠送一定额度的 B/C 类操作次数,免费出口流量是 3 倍存储量。对于增量备份,成本几乎可以忽略不计了。

假设我每月存储 100GB,费用大约为 4.12 元/月左右,实际根本用不了这么多。
创建访问机密
在 B2 创建存储桶,在 Application Keys 里生成访问机密,记下 keyID 和 applicationKey 即可开始配置。
安装 Kopia#
在 Debian GNU/Linux 上,最省心的安装方式是添加官方的 APT 仓库。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# 导入 GPG 密钥
curl -s https://kopia.io/signing-key | sudo gpg --dearmor -o /etc/apt/keyrings/kopia-keyring.gpg
# 添加软件源
echo "deb [signed-by=/etc/apt/keyrings/kopia-keyring.gpg] http://packages.kopia.io/apt/ stable main" | sudo tee /etc/apt/sources.list.d/kopia.list
# 安装
sudo apt update && sudo apt install kopia
# 验证安装
kopia --version
|
配置 Kopia#
初始化存储库#
执行 repository create 命令初始化存储库:
1
2
3
4
5
6
7
8
9
|
sudo kopia repository create b2 \
--bucket=<存储桶名称> \
--key-id=<keyID> \
--key=<applicationKey> \
--prefix=<目录前缀>/
|
务必牢记设置的密码 ,一旦遗忘,备份数据将彻底锁定,没有找回或重置密码的可能。
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
|
Enter password to create new repository:
Re-enter password for verification:
Initializing repository with:
block hash: BLAKE2B-256-128
encryption: AES256-GCM-HMAC-SHA256
key derivation: scrypt-65536-8-1
splitter: DYNAMIC-4M-BUZHASH
Connected to repository.
NOTICE: Kopia will check for updates on GitHub every 7 days, starting 24 hours after first use.
To disable this behavior, set environment variable KOPIA_CHECK_FOR_UPDATES=false
Alternatively you can remove the file "/root/.config/kopia/repository.config.update-info.json".
Retention:
Annual snapshots: 3 (defined for this target)
Monthly snapshots: 24 (defined for this target)
Weekly snapshots: 4 (defined for this target)
Daily snapshots: 7 (defined for this target)
Hourly snapshots: 48 (defined for this target)
Latest snapshots: 10 (defined for this target)
Ignore identical snapshots: false (defined for this target)
Compression disabled.
To find more information about default policy run 'kopia policy get'.
To change the policy use 'kopia policy set' command.
NOTE: Kopia will perform quick maintenance of the repository automatically every 1h0m0s
and full maintenance every 24h0m0s when running as root@vps-micro.
See https://kopia.io/docs/advanced/maintenance/ for more information.
NOTE: To validate that your provider is compatible with Kopia, please run:
$ kopia repository validate-provider
|
查看连接状态
1
|
sudo kopia repository status
|
设置全局策略#
设置全局压缩算法,推荐 zstd 选项,平衡压缩率和速度
1
|
sudo kopia policy set --global --compression=zstd
|
配置全局保留策略,作为备份项目的回退配置
1
2
3
|
# 保留最新的 8 份快照
sudo kopia policy set --global --keep-latest 8
|
检查全局策略是否生效
自动化备份#
以 /home/dejavu/warp 下的 Docker 容器为例,该容器使用了绑定挂载。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
# 细致化单个备份项目保留策略
# 对于配置基本不动的容器,保留 3 份快照足矣
sudo kopia policy set /home/dejavu/warp \
--keep-latest 3 \
--keep-hourly 0 \
--keep-daily 0 \
--keep-weekly 0 \
--keep-monthly 0 \
--keep-annual 0
|
排除无关文件#
通过 .kopiaignore 文件,排除掉日志、缓存等无用数据,比如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# 忽略日志文件
*.log
logs/
# 忽略临时目录
tmp/
temp/
# 排除缓存
.cache/
|
检查具体备份项目的策略
1
|
sudo kopia policy get /home/dejavu/warp
|
自动化脚本#
Actions 是 Kopia 内置的一种 钩子(Hooks)机制 。能在快照执行前后自动触发命令,但对我而言,这种「黑盒」机制调试起来不够直观,本文不使用该方式。
我选择编写一个简单的 Shell 脚本,配合 Crontab 定时执行。
1
2
3
4
5
6
7
|
# 创建日志目录
sudo mkdir -p /root/kopia/logs
# 创建备份脚本
sudo vim /root/kopia/backup-warp.sh
|
示例脚本如下:
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
|
#!/bin/bash
SRC_DIR="/home/dejavu/warp"
LOG_DIR="/root/kopia/logs"
CURRENT_DATE=$(date +%Y%m%d)
LOG_FILE="$LOG_DIR/${CURRENT_DATE}-warp.log"
# 必须修改 Kopia 存储库密码(使用单引号包裹)
export KOPIA_PASSWORD=''
# [可选] 禁用 Kopia 检查更新(适合国内机器)
# export KOPIA_CHECK_FOR_UPDATES=false
log() {
echo "[$(date '+%H:%M:%S')] $1" >> "$LOG_FILE"
}
# 回退
fallback_service() {
# 检查容器状态
if ! docker compose -f "$SRC_DIR/compose.yml" ps --services --filter "status=running" | grep -q "warp"; then
log "恢复容器启动..."
docker compose -f "$SRC_DIR/compose.yml" up -d >> "$LOG_FILE" 2>&1
fi
}
trap fallback_service EXIT
log "=== 备份开始 ==="
cd "$SRC_DIR" || { log "致命错误:找不到目录 $SRC_DIR"; exit 1; }
log "1. 停止容器..."
docker compose down >> "$LOG_FILE" 2>&1
if [ $? -ne 0 ]; then
log "❌ 容器停止失败,跳过备份以保数据安全。"
exit 1
fi
# 4.3 创建快照 (耗时操作)
log "开始创建快照..."
kopia snapshot create "$SRC_DIR" --description "Weekly Backup" >> "$LOG_FILE" 2>&1
SNAPSHOT_STATUS=$?
# 启动服务
log "3. 正在恢复服务..."
docker compose up -d >> "$LOG_FILE" 2>&1
if [ $SNAPSHOT_STATUS -eq 0 ]; then
log "✅ 快照创建成功"
else
log "❌ 快照创建失败!"
exit 1
fi
# 维护任务
log "执行存储库维护 (GC)..."
kopia maintenance run --full >> "$LOG_FILE" 2>&1
log "=== 备份结束 ==="
|
设置脚本权限
1
|
sudo chmod 700 /root/kopia/backup-warp.sh
|
手动执行一次
1
|
sudo /root/kopia/backup-warp.sh
|
检查首次运行生成的日志
1
|
cat /root/kopia/logs/20260201-warp.log
|
查看了 B2 存储桶,快照已同步,日志输出也符合预期:
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
|
[10:26:27] === 备份开始 ===
[10:26:27] 1. 停止容器...
Container warp Stopping
Container warp Stopped
Container warp Removing
Container warp Removed
Network warp-tunnel Removing
Network warp-tunnel Resource is still in use
[10:26:37] 开始创建快照...
Snapshotting root@vps-micro:/home/dejavu/warp ...
* 0 hashing, 40 hashed (23.4 MB), 0 cached (0 B), uploaded 193 B, estimated 23.4 MB (100.0%) 0s left
Created snapshot with root ka463aa955f638a00aed18d636e77e60c and ID 72c3275ef74463396ad6092d49c84ff0 in 1s
Running quick maintenance...
Compacting an eligible uncompacted epoch...
Advancing epoch markers...
Finished quick maintenance.
[10:26:46] 3. 正在恢复服务...
Container warp Creating
Container warp Created
Container warp Starting
Container warp Started
[10:26:47] ✅ 快照创建成功
[10:26:47] 执行存储库维护 (GC)...
Running full maintenance...
GC found 0 unused contents (0 B)
GC found 0 unused contents that are too recent to delete (0 B)
GC found 47 in-use contents (1.3 MB)
GC found 5 in-use system-contents (2.7 KB)
GC undeleted 0 contents (0 B)
Compacting an eligible uncompacted epoch...
Advancing epoch markers...
Attempting to compact a range of epoch indexes ...
Cleaning up unneeded epoch markers...
Cleaning up old index blobs which have already been compacted...
Cleaned up 0 logs.
Finished full maintenance.
[10:26:56] === 备份结束 ===
|
备份自动通知 [可选]#
这是个可选配置,但感觉蛮实用的。我们使用 Apprise 推送通知,下面以 Telegram 为例。
使用 Docker Compose 启动一个 Apprise 服务
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
|
services:
apprise:
image: caronc/apprise:latest
container_name: apprise
restart: unless-stopped
user: "1000"
networks:
- apprise
environment:
APPRISE_STATEFUL_MODE: simple
APPRISE_WORKER_COUNT: "1"
volumes:
- ./config:/config
- ./plugin:/plugin
- ./attach:/attach
networks:
apprise:
name: apprise
driver: bridge
enable_ipv6: true
|
修改自动化脚本
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
|
#!/bin/bash
SRC_DIR="/home/dejavu/warp"
LOG_DIR="/root/kopia/logs"
CURRENT_DATE=$(date +%Y%m%d)
LOG_FILE="$LOG_DIR/${CURRENT_DATE}-warp.log"
# ================= 配置区域 =================
# Kopia 存储库密码
export KOPIA_PASSWORD=''
# [可选] 禁用 Kopia 检查更新
# export KOPIA_CHECK_FOR_UPDATES=false
# Apprise 通知配置
NOTIFICATION_URL="tgram://<botToken>/<group>/"
APPRISE_NET="apprise"
APPRISE_API="http://apprise:8000/notify"
# ===========================================
log() {
echo "[$(date '+%H:%M:%S')] $1" >> "$LOG_FILE"
}
# 发送通知函数
send_notify() {
local STATUS_ICON=$1
local STATUS_MSG=$2
local DETAIL_MSG=$3
local DATE_STR=$(date +%Y-%m-%d)
local TITLE="Kopia备份 - Warp"
local BODY="${STATUS_ICON} ${DATE_STR}-warp-${STATUS_MSG}\n\n${DETAIL_MSG}"
local JSON_PAYLOAD=$(cat <<EOF
{
"urls": "$NOTIFICATION_URL",
"title": "$TITLE",
"body": "$BODY"
}
EOF
)
log "正在发送通知..."
docker run --rm --network "$APPRISE_NET" curlimages/curl:8.18.0 \
-s -o /dev/null -X POST \
-H "Content-Type: application/json" \
-d "$JSON_PAYLOAD" \
"$APPRISE_API"
}
# 回退函数
fallback_service() {
if ! docker compose -f "$SRC_DIR/compose.yml" ps --services --filter "status=running" | grep -q "warp"; then
log "恢复容器启动..."
docker compose -f "$SRC_DIR/compose.yml" up -d >> "$LOG_FILE" 2>&1
fi
}
trap fallback_service EXIT
log "=== 备份开始 ==="
cd "$SRC_DIR" || { log "致命错误:找不到目录 $SRC_DIR"; exit 1; }
log "1. 停止容器..."
docker compose down >> "$LOG_FILE" 2>&1
if [ $? -ne 0 ]; then
log "❌ 容器停止失败,跳过备份以保数据安全。"
send_notify "❌" "备份中断" "原因:容器无法正常停止,未执行备份。"
exit 1
fi
# 创建快照
log "开始创建快照..."
# 捕获输出以便在通知中显示部分信息
SNAPSHOT_OUTPUT=$(kopia snapshot create "$SRC_DIR" --description "Weekly Backup" 2>&1 | tee -a "$LOG_FILE")
SNAPSHOT_STATUS=${PIPESTATUS[0]}
# 启动服务
log "正在恢复服务..."
docker compose up -d >> "$LOG_FILE" 2>&1
# 获取单纯的日志文件名 (例如: 20260202-warp.log)
LOG_FILENAME=$(basename "$LOG_FILE")
if [ $SNAPSHOT_STATUS -eq 0 ]; then
log "✅ 快照创建成功"
# 提取快照 ID
SNAP_ID=$(grep "Created snapshot with root" "$LOG_FILE" | tail -n 1 | awk '{print $5}')
[ -z "$SNAP_ID" ] && SNAP_ID="ID提取异常"
# === 修改点:这里的日志位置变量改为了 $LOG_FILENAME ===
send_notify "✅" "备份成功!" "快照ID: $SNAP_ID\n服务已恢复运行。\n日志位置: $LOG_FILENAME"
else
log "❌ 快照创建失败!"
# 失败时也只显示文件名,保持整洁
send_notify "❌" "备份失败!" "Kopia 返回了错误代码。\n请立即检查服务器日志: $LOG_FILENAME"
exit 1
fi
# 维护任务
log "执行存储库维护 (GC)..."
kopia maintenance run --full >> "$LOG_FILE" 2>&1
log "=== 备份结束 ==="
|
示例效果:
1
2
3
4
5
6
7
8
9
|
Kopia备份 - Warp
✅ 2026-02-02-warp-备份成功!
快照ID: ke5e0d06612cb9850a1172cf0242983fb
服务已恢复运行。
日志位置: 20260202-warp.log
|
配置定时任务#
通过 Crontab 自动化执行任务
末尾添加一项任务
1
2
3
4
5
|
# 由于我的服务器时区设置为 UTC 世界协调时
# 预期:北京时间每周一的 01:00 执行
0 17 * * 0 /bin/bash /root/kopia/backup-warp.sh
|
恢复备份#
新建一个测试目录,验证一遍恢复流程。
1
|
mkdir -p /tmp/warp_test_restore
|
常规恢复#
如果需要找回最近的数据,直接执行:
1
2
3
4
5
6
7
|
# 格式:snapshot restore <源路径> <恢复目标>
sudo kopia snapshot restore /home/dejavu/warp /tmp/warp_test_restore/latest
# 验证恢复
ls -lah /tmp/warp_test_restore/latest
|
回溯到特定时间点的历史快照版本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
# 查看快照历史
sudo kopia snapshot list /home/dejavu/warp
# 示例输出:
root@vps-micro:/home/dejavu/warp
2026-02-01 10:26:40 UTC ka463aa955f638a00aed18d636e77e60c 23.4 MB drwxrwxr-x files:40 dirs:6 (latest-1)
# 使用 ID 指定快照版本
sudo kopia snapshot restore ka463aa955f638a00aed18d636e77e60c /tmp/warp_test_restore/history
# 验证恢复
ls -lah /tmp/warp_test_restore/history
|
目前,看起来一切都工作的很好。
灾难恢复#
现在我们要考虑极端情况,若服务器数据被完全损坏,如何在新环境下进行灾难恢复呢?
- 在新机器上安装 Kopia
- 使用相同的 B2 配置连接现有的存储桶
1
2
3
4
5
|
sudo kopia repository connect b2 \
--bucket=<存储桶名称> \
--key-id=<keyID> \
--key=<applicationKey> \
--prefix=<目录前缀>/
|
- 新机器的主机名可能不同,需查看所有已存在快照
1
2
3
4
|
sudo kopia snapshot list --all
# 找到之前机器的<用户>@<主机名>:<备份目录>
root@vps-micro:/home/dejavu/warp
2026-02-01 10:26:40 UTC ka463aa955f638a00aed18d636e77e60c 23.4 MB drwxrwxr-x files:40 dirs:6 (latest-1)
|
- 恢复数据
1
2
|
# 用法:snapshot restore <用户>@<旧主机名>:<原路径> <目标路径>
sudo kopia snapshot restore ka463aa955f638a00aed18d636e77e60c root@vps-micro:/home/dejavu/warp /tmp/warp_restore_test
|
- 若想让新机器 彻底接管 旧机器的 增量备份历史 ,连接时需「伪装」身份。
1
2
3
4
5
6
7
8
9
|
# 断开并使用 override 参数重新连接
sudo kopia repository disconnect
sudo kopia repository connect b2 \
--bucket=<存储桶名称> \
--key-id=<keyID> \
--key=<applicationKey> \
--prefix=<目录前缀>/
--override-hostname=<原主机名> \
--override-username=<原用户名>
|
验证完成,删除测试路径
1
|
sudo rm -rf /tmp/warp_test_restore
|
至此,我们的自动化备份方案就实施完成了,后续添加新的自动备份任务,只需要:
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
|
# 细致化策略
sudo kopia policy set /home/dejavu/wakapi \
--keep-latest 3 \
--keep-hourly 0 \
--keep-daily 0 \
--keep-weekly 0 \
--keep-monthly 0 \
--keep-annual 0
# 验证策略
sudo kopia policy get /home/dejavu/wakapi
# 编辑自动化任务
sudo vim /root/kopia/backup-wakapi.sh
# 设置权限
sudo chmod 700 /root/kopia/backup-wakapi.sh
# 首次备份
sudo /bin/bash /root/kopia/backup-wakapi.sh
# 定时任务
sudo crontab -e
|
当然,你还可以考虑做这些优化:
- 错峰备份,分散单日存储桶操作数,降低成本
- 多存储服务商、多区域冗余,提升抗灾害能力