常用环境: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 default
root@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

⭐ 七、现在拥有的功能

功能

状态

每日自动备份

使用 R2 存储,不经过 Cloudflare CDN

pack-size=256MB,高效上传

自动比较大小变化

自动发送 snapshot 历史

自动发送仓库统计

自动错误通知

定时器按北京时间执行

手动备份命令


⭐ 八、待扩展

🟦 数据库自动备份(MySQL / MariaDB)
🟦 Docker Compose 全量元数据备份
🟦 R2 Lifecycle(删除老碎片)
🟦 多服务器统一备份方案
🟦 Cloudflare Zero Trust 限制 R2 访问