常用环境:BT面板+docker
🟦 ResticProfile + Docker
🟦 使用 Cloudflare R2 作为备份仓库
🟦 自动每日备份(北京时间)
🟦 systemd 定时器(兼容 Debian12 的 systemd 252)
🟦 高质量通知(大小变化、快照历史、仓库统计)
🟦 错误通知
🟦 自动清理旧快照(retention)
🟦 手动执行备份的方法
🟦 方案解释
所有步骤连贯、可直接复制即可使用。
⭐ 一、目录结构(推荐)
/www/wwwroot/docker/restic/
│── docker-compose.yaml
│── config/
│ ├── profiles.yaml
│ └── password
⭐ 二、docker-compose.yaml
文件:docker-compose.yaml
services:
restic:
image: creativeprojects/resticprofile:latest
container_name: restic-backup
restart: unless-stopped
privileged: true
hostname: root-server
command: "sleep infinity"
volumes:
- /:/host:ro,rslave
- ./config/profiles.yaml:/etc/resticprofile/profiles.yaml
- ./config/password:/etc/resticprofile/password # 文件内输入restic要是用的密码,后续还原等要用
environment:
TZ: Asia/Shanghai # 容器内用中国时间以便生成报告更易读
🚀 启动容器
docker compose up -d
⭐ 三、profiles.yaml(你的主配置)
文件:config/profiles.yaml
⚠️ 这个版本已修复你所有之前的问题(-r 错误,JSON 格式错误,字段错位等)。已实战验证可用。
version: "1"
global:
scheduler: internal
default:
repository: "s3:https://[r2路径]/[存储桶名称]/system-backup"
password-file: "/etc/resticprofile/password"
force-inactive-lock: true
env:
AWS_ACCESS_KEY_ID: "[S3ID]"
AWS_SECRET_ACCESS_KEY: "[S3密钥]"
AWS_DEFAULT_REGION: "auto"
AWS_ENDPOINT: "[r2路径]"
backup:
source:
- "/host/www"
- "/host/var/lib/docker"
exclude:
- "/host/var/lib/docker/overlay2"
- "/host/var/lib/docker/containers/*-json.log"
- "/host/var/lib/docker/buildkit"
- "/host/var/lib/docker/tmp"
option:
- "pack-size=256m" # 根据自己的网络到存储桶的带宽设置
tag:
- "full-system-backup"
schedule: "*-*-* 05:00:00" # 仅占位,不真正使用
run-after:
- |
#!/bin/sh
set -e
export RESTIC_PASSWORD_FILE="/etc/resticprofile/password"
RESTIC_REPOSITORY="s3:https://5b82ce1c45b6ae0e6759be700bf0342e.r2.cloudflarestorage.com/root-server-backup/system-backup"
NOW="$(date '+%F %T')"
# 获取最新的两个快照 ID
LATEST_ID=$(restic -r "$RESTIC_REPOSITORY" snapshots --compact --latest 1 | awk 'NR==3 {print $1}')
PREV_ID=$(restic -r "$RESTIC_REPOSITORY" snapshots --compact --latest 2 | awk 'NR==3 {print $1}')
[ "$PREV_ID" = "$LATEST_ID" ] && PREV_ID=""
# === 修复开始 ===
# 1. 使用 --json 获取精确字节数
# 2. 去掉 --mode raw-data,默认使用 restore-size (与快照列表一致)
# 3. 使用 sed 提取 total_size 的值
LATEST_JSON=$(restic -r "$RESTIC_REPOSITORY" stats --json "$LATEST_ID")
CUR_BYTES=$(echo "$LATEST_JSON" | sed -n 's/.*"total_size": *\([0-9]*\).*/\1/p')
[ -z "$CUR_BYTES" ] && CUR_BYTES=0
CUR_MB=$((CUR_BYTES / 1024 / 1024))
if [ -n "$PREV_ID" ]; then
PREV_JSON=$(restic -r "$RESTIC_REPOSITORY" stats --json "$PREV_ID")
PREV_BYTES=$(echo "$PREV_JSON" | sed -n 's/.*"total_size": *\([0-9]*\).*/\1/p')
[ -z "$PREV_BYTES" ] && PREV_BYTES=0
PREV_MB=$((PREV_BYTES / 1024 / 1024))
DIFF=$((CUR_MB - PREV_MB))
if [ "$DIFF" -gt 0 ]; then
DIFF_TEXT="较上次增加 ${DIFF} MiB 📈"
elif [ "$DIFF" -lt 0 ]; then
DIFF_ABS=$(( -DIFF ))
DIFF_TEXT="较上次减少 ${DIFF_ABS} MiB 📉"
else
DIFF_TEXT="与上次一致(无变化) ⚖️"
fi
else
DIFF_TEXT="🚀 首次备份,无历史对比"
fi
# === 修复结束 ===
SNAPSHOTS=$(restic -r "$RESTIC_REPOSITORY" snapshots --compact --latest 5)
# 这里的 stats 保持 raw-data 没问题,用于查看压缩率
STATS=$(restic -r "$RESTIC_REPOSITORY" stats --mode raw-data)
curl -sS -X POST -H "Content-Type: application/json" -d @- \
"https://notify.beiai.de/relay/PQ56Ujtsa7ZR/e776d819b9818824f2a6fb1146fc7326d0a5532e9fa12e6126455154efe80dfa/SEC5c65d82fea0ffa5b34d957518c7d1061823ee5f3df4535d72af3de4674291f15" <<EOF
{
"msgtype": "markdown",
"markdown": {
"title": "📦 root-server Restic 备份成功",
"text": "### 🎉 系统备份成功\n\n- **时间:** ${NOW}\n- **当前快照:** ${LATEST_ID}\n- **大小:** ${CUR_MB} MiB\n- **变化:** ${DIFF_TEXT}\n\n---\n#### 📜 最近 5 条快照\n\n\`\`\`\n${SNAPSHOTS}\n\`\`\`\n\n---\n#### 💾 仓库统计(压缩后占用)\n\n\`\`\`\n${STATS}\n\`\`\`"
}
}
EOF
run-after-fail:
- |
NOW="$(date '+%F %T')"
curl -sS -X POST -H "Content-Type: application/json" -d @- \
"https://notify.beiai.de/relay/PQ56Ujtsa7ZR/[钉钉机器人token]/[钉钉机器人加签密钥]" <<EOF
{
"msgtype": "markdown",
"markdown": {
"title": "❌ root-server 系统备份失败",
"text": "### ⚠️ 备份任务失败\n\n- **时间:** ${NOW}\n- **请检查:** \`docker logs restic-backup --tail 200\`"
}
}
EOF
retention:
keep-daily: 7
keep-weekly: 4
keep-monthly: 6
prune: true
⭐ 四、初始化 R2 仓库(只做一次)
docker compose run --rm restic init
⭐ 五、手动执行备份测试(最关键)
docker compose run --rm restic backup --profile defaultroot@beiai:/www/wwwroot/docker/restic# docker compose run --rm restic backup --profile default
2025/11/24 20:52:24 using configuration file: /etc/resticprofile/profiles.yaml
2025/11/24 20:52:24 profile 'default': starting 'backup'
default does not exist, skipping
repository 686cce90 opened (version 2, compression level auto)
created new cache in /root/.cache/restic
using parent snapshot 003c0271
[0:00] 100.00% 9 / 9 index files loaded
Files: 56 new, 83 changed, 45948 unmodified
Dirs: 6 new, 165 changed, 4462 unmodified
Added to the repository: 27.609 MiB (4.691 MiB stored)
processed 46087 files, 3.027 GiB in 0:03
snapshot f0d7d7a8 saved
2025/11/24 20:52:28 profile 'default': finished 'backup'
{"payloadType":"json","queued":1,"request_id":"98f9909f9b874e3f","success":true}
root@beiai:/www/wwwroot/docker/restic# 如果成功,会立即收到通知。
⭐ 六、systemd 定时任务(在美国服务器)
Debian12 的 systemd 不支持 Timezone=
我们必须 手动做北京时间 → PST 时间换算
每天北京时间 05:00 = 前一天 13:00 PST
创建 Timer:
nano /etc/systemd/system/restic-backup.timer
内容:
[Unit]
Description=Restic Backup Daily Timer (Beijing Time)
[Timer]
OnCalendar=*-*-* 13:00:00
Unit=restic-backup.service
Persistent=true
[Install]
WantedBy=timers.target
再创建 service:
nano /etc/systemd/system/restic-backup.service
内容:
[Unit]
Description=Run Restic Backup via Docker
[Service]
Type=oneshot
WorkingDirectory=/www/wwwroot/docker/restic
ExecStart=/usr/bin/docker compose run --rm restic backup --profile default
启用:
systemctl daemon-reload
systemctl enable --now restic-backup.timer
查看:
systemctl status restic-backup.timer
systemctl list-timers | grep restic
⭐ 七、现在拥有的功能
⭐ 八、待扩展
🟦 数据库自动备份(MySQL / MariaDB)
🟦 Docker Compose 全量元数据备份
🟦 R2 Lifecycle(删除老碎片)
🟦 多服务器统一备份方案
🟦 Cloudflare Zero Trust 限制 R2 访问