这是什么?怎么使用?请看上一篇 Duplicacy CLI 备份工具基本使用笔记

排除/包含过滤器

对于一个 Repository,排除/包含过滤器适用于 Duplicacy 的备份 (backup) 和恢复 (restore) 命令

  • 备份 (backup) 过滤器默认使用 .duplicacy 目录下的 filters 文件
  • 恢复 (restore) 的过滤器需要指定命令行参数使用

这样设计是合理的,备份一般会被设置为自动化的定时任务,读取文件或目录过滤规则会很方便; 恢复操作一般是用户手动操作,备份的时候设置好了过滤规则,恢复的时候就无需再使用过滤器

Duplicacy 的过滤器支持 (*)通配符匹配正则表达式,可以单独使用任一种或是将两种结合起来使用。

模式的顺序很重要,通过模式匹配路径的时候,Duplicacy 对模式按照 路径顺序 一级一级的匹配,若发现该路径与包含模式匹配,表示该路径被包含在内且不再比较;同理,若发现该路径与排除模式相匹配也不再进一步比较。找不到匹配的时候,如果所有模式都是包含,该路径将被排除,否则将被包含。

注意: 路径相对于当前 Repository,区分大小写,在所有系统上路径都没有前导符号 /

测试过滤器

Duplicacy 的排除/包含过滤器相当强大和灵活,但是实际使用的时候,可能会让您有点困扰。保持简单的原则,我们举几个简单的例子,然后使用命令来测试过滤器(这实际并不会真的备份),它会告诉我们这个过程过滤器是如何工作的。

$ duplicacy -d -log backup -enum-only

编写过滤器规则时,可以执行上面的测试命令来验证过滤器是否正确工作。

通配符匹配

包含模式+ 开头,排除模式- 开头;所有模式都可以使用 * 通配符匹配任意长度的路径字符串,使用 ? 匹配单个字符;*? 支持匹配包括 / 在内的任何字符。

/ 结尾的模式仅适用于目录,文件不以 / 结尾;以 *? 结尾的模式适用于目录或文件,当一个目录被排除时,该目录下所有文件和子目录都会被排除,所以要包含某个子目录,必须明确包含其所有父目录。

演示的文件目录树如下:

$ tree -L 3 -a
.
├── .DS_Store
├── .duplicacy
│   ├── .DS_Store
│   ├── cache
│   │   ├── .DS_Store
│   │   └── default
│   ├── filters
│   ├── odb-token.json
│   └── preferences
├── LICENSE
├── README.md
├── config.json
├── docs
│   ├── contribute.md
│   ├── deploy.md
│   ├── develop.md
│   └── get-start.md
├── foo
│   ├── bar
│   │   ├── 1-1.md
│   │   ├── 2-2.md
│   │   └── 3-3.md
│   └── po
│       └── hello.md
├── tmp
│   ├── .DS_Store
│   ├── download.tmp
│   └── upload.tmp
└── upload
    ├── telegram-upload
    └── gpg-fingerprint-filter-gpu

9 directories, 22 files

情景 1 只想要备份 foo/bar/ 目录,我们可能会这样写:

# 错误示例
+foo/bar/*
-foo/po/
-*

看起来似乎没问题,但是 Duplicacy 并不会如期工作,它的过滤器按照 路径顺序 一级一级匹配,当它匹配到 -* 排除所有目录的时候,就会把 foo/ 目录直接排除(第一级在同一级),foo/ 的子目录 bar/ 根本就不会被访问到(直接被排除了).

我们要确保 foo/ 先被包含,然后它下面的 bar/ 目录才会被包含,就像这样:

# 正确示例
+foo/
+foo/bar/*
-foo/po/
-*

测试过滤器工作过程

INFO SNAPSHOT_FILTER Parsing filter file /Users/xvoes/Space/clink/.duplicacy/filters
DEBUG REGEX_DEBUG There are 0 compiled regular expressions stored
INFO SNAPSHOT_FILTER Loaded 4 include/exclude pattern(s)
TRACE SNAPSHOT_PATTERN Pattern: +foo/
TRACE SNAPSHOT_PATTERN Pattern: +foo/bar/*
TRACE SNAPSHOT_PATTERN Pattern: -foo/po/
TRACE SNAPSHOT_PATTERN Pattern: -*
DEBUG LIST_ENTRIES Listing
DEBUG PATTERN_EXCLUDE .DS_Store is excluded by pattern -*
DEBUG PATTERN_EXCLUDE LICENSE is excluded by pattern -*
DEBUG PATTERN_EXCLUDE README.md is excluded by pattern -*
DEBUG PATTERN_EXCLUDE config.json is excluded by pattern -*
DEBUG PATTERN_EXCLUDE docs/ is excluded by pattern -*
DEBUG PATTERN_INCLUDE foo/ is included by pattern +foo/
DEBUG CHUNK_CACHE Chunk af8a4271e55cc0617ce5e5c31b9e8a0419cd6e6322f79e138213dcdf7c15c952 has been loaded from the snapshot cache
DEBUG PATTERN_EXCLUDE tmp/ is excluded by pattern -*
DEBUG PATTERN_EXCLUDE upload/ is excluded by pattern -*
DEBUG LIST_ENTRIES Listing foo/
DEBUG PATTERN_INCLUDE foo/bar/ is included by pattern +foo/bar/*
DEBUG PATTERN_EXCLUDE foo/po/ is excluded by pattern -foo/po/
DEBUG LIST_ENTRIES Listing foo/bar/
DEBUG PATTERN_INCLUDE foo/bar/1-1.md is included by pattern +foo/bar/*
DEBUG PATTERN_INCLUDE foo/bar/2-2.md is included by pattern +foo/bar/*
DEBUG PATTERN_INCLUDE foo/bar/3-3.md is included by pattern +foo/bar/*

情景 2 只包含 foo/ 并且排除里面的 po/ 子目录:

-foo/bar/
-foo/po/
+foo/*
-*

情景 3 包含 upload/ 目录并排除里面的所有文件

+upload/
-upload/?*
-*

情景 4 只包含 foo/ 排除里面所有的子目录

-foo/*/*
+foo/
-*

情景 4 只包含所有 .md 文件

+docs/
+foo/
+?*.md
-*

测试过滤器,可以看到正确工作了

INFO SNAPSHOT_FILTER Parsing filter file /Users/xvoes/Space/clink/.duplicacy/filters
DEBUG REGEX_DEBUG There are 0 compiled regular expressions stored
INFO SNAPSHOT_FILTER Loaded 5 include/exclude pattern(s)
TRACE SNAPSHOT_PATTERN Pattern: -?*.DS_Store
TRACE SNAPSHOT_PATTERN Pattern: +docs/
TRACE SNAPSHOT_PATTERN Pattern: +foo/
TRACE SNAPSHOT_PATTERN Pattern: +?*.md
TRACE SNAPSHOT_PATTERN Pattern: -*
DEBUG LIST_ENTRIES Listing
DEBUG PATTERN_EXCLUDE .DS_Store is excluded by pattern -*
DEBUG PATTERN_EXCLUDE LICENSE is excluded by pattern -*
DEBUG PATTERN_INCLUDE README.md is included by pattern +?*.md
DEBUG PATTERN_EXCLUDE config.json is excluded by pattern -*
DEBUG PATTERN_INCLUDE docs/ is included by pattern +docs/
DEBUG PATTERN_INCLUDE foo/ is included by pattern +foo/
DEBUG PATTERN_EXCLUDE tmp/ is excluded by pattern -*
DEBUG PATTERN_EXCLUDE upload/ is excluded by pattern -*
DEBUG LIST_ENTRIES Listing docs/
DEBUG PATTERN_INCLUDE docs/contribute.md is included by pattern +?*.md
DEBUG PATTERN_INCLUDE docs/deploy.md is included by pattern +?*.md
DEBUG PATTERN_INCLUDE docs/develop.md is included by pattern +?*.md
DEBUG PATTERN_INCLUDE docs/get-start.md is included by pattern +?*.md
DEBUG LIST_ENTRIES Listing foo/
DEBUG PATTERN_EXCLUDE foo/bar/ is excluded by pattern -*
DEBUG PATTERN_EXCLUDE foo/po/ is excluded by pattern -*

正则表达式

包含模式i: 开头,排除模式e: 开头,过滤器规则顺序和前面一样,正则表达式更为强大,Duplicacy 使用标准 正则表达式,如果没接触过需要学习一下。

# 包含 SQLite 数据库文件
i:\.sqlite$
# 排除 SQLite 临时文件
e:\.sqlite-.*$

导入过滤器

默认过滤器文件是 .duplicacy/filters,还可以使用 @ 导入其他的过滤器文件,比如:

@/path/to/the/one-filters-file
@/path/to/the/two-filters-file

过滤器路径

修改 Repository 默认的过滤器文件位置

$ duplicacy set -filters new-filters
New options for storage odb://backup have been saved

直接修改 preference 里的配置也是可以的 😂

Preferance

密码凭据管理

大多数桌面操作系统有一个 Keyring/Keychain 保险库,默认情况下 1,Duplicacy 使用它存储用户输入的密码和 Storage 服务的相关机密信息。

Duplicacy 在 macOS 上使用 Keychain,在 Linux 上仅支持 Gome-Keyring 2,在 Windows 上使用一组加密/解密 API 处理凭据并保存到 .duplicacy/keyring 文件里。

一般服务器上很少使用桌面环境,自然也没有 Duplicacy 支持的密码凭据保险库。这就导致每次用户执行 duplicacy 命令都需要输入加密密码、Storage 的相关凭据,这对我们之后的自动化任务是很不利的 3。Duplicacy 提供了两种方法来保存密码凭据,一种是将密码凭据暴露到环境变量 4,各种不同的密码凭据 环境变量名称 可以在 Wiki 页面 查看。

在终端使用 export 将密码凭据导入当前终端会话里 5,需要注意的是, 环境变量名称都应该全大写(即使您的 Storage 名称是小写),比如:

export DUPLICACY_PASSWORD="Your-Repository-Encrypt-Password"
export DUPLICACY_S3_ID="Your-S3-API-ID"
export DUPLICACY_S3_SECRET="Your-S3-API-Secret"

# 比如将 Azure Access Key 保存到一个文本文件
export DUPLICACY_AZURE_KEY="cat /home/ubuntu/azure_access_password"

另一种方法是将密码凭据相关的信息存储到 .duplicacy/preferences 文件里,这是本文推荐的方法,可以在多个 Repository 之间独立分离密码凭据

└── .duplicacy
    ├── cache
    ├── filters
    └── preferences

为了安全,在 Linux 服务器上应该禁止 root 用户直接登录,日常使用普通用户,需要 root 权限的时候用 sudo 命令进行提权。在这个前提下,要确保 .duplicacy/ 目录仅能被 root 用户读写,普通用户不可读写,虽谈不上有多么安全,但至少比环境变量保存密码凭据要好得多。

下面操作的普通用户是 xvoes,Repository 即将在 xvoes 的 container/example 里初始化,使用的存储是 Cloudflare R2 对象存储(兼容 S3 API)

# 当前用户路径
$ pwd
/home/xvoes

# 进入目标目录 (注意修改)
$ cd container/example

# 初始化 Repository
$ sudo duplicacy init -e -storage-name r2 vaultwarden s3://[email protected]/xxx

接下来把密码凭据保存到 .duplicacy/preferences6

$ sudo duplicacy set -key r2_s3_id -value "xxxxxxxxxxx"
$ sudo duplicacy set -key r2_s3_secret -value "xxxxxxxxxxxxxxx"
$ sudo duplicacy set -key password -value "xxxxxxxxxxxxx"

创建排除/包含过滤器规则文件并测试

$ cd .duplicacy/ && \
touch filters && \
vim filters

# 编辑完成后,测试过滤器规则
$ sudo duplicacy -d -log backup -enum-only

控制密码凭据父目录仅 root 用户可读写

$ sudo chmod -R 0700 .duplicacy

进行第一次备份,应该不需要输入任何凭据或密码了,否则请检查

$ sudo duplicacy -log backup -stats

定时任务

在 Linux 上使用 Cron 运行定时任务是个比较简单高效的方案,我们的任务很简单,写个 .sh 脚本定时用 Cron 执行,将执行时间和日志保存到指定位置,简单的示例:

#!/bin/sh
# backup-example.sh (示例脚本名称)
cd /home/xvoes/container/example  # 修改为您的 Repository 路径 
echo "`date -Iseconds`" Started backing up $PWD ...
sudo duplicacy -log backup -stats
echo "`date -Iseconds`" Stopped backing up $PWD ...

给脚本 backup-example.sh 可执行权限

$ sudo chmod +x backup-example.sh

建议运行一次脚本测试一下

./backup-example.sh

确认没问题就可以设置 Cron 定时任务了

$ sudo crontab -e

# 编辑 Ctontab 任务
# 以每天的 3 点 33 分执行任务脚本,并将日志以时间格式保存到指定位置 (注意修改路径)
33 3 * * * cd /home/xvoes/ && ./backup-example.sh > /home/xvoes/duplicacy/logs/backup-example_`date "+\%Y-\%m-\%d-\%H-\%M"`.log 2>&1

多任务示例

上面是一台服务器上的单个备份任务,这次操作在另一台服务器上,定时备份多个任务,已知:

  • 服务器上运行服务:kutt, metube, rsstt, uptime-kuma, zlib-searcher, cloudflared
  • 服务器的系统时钟是 UTC 时间
  • 服务器操作的普通用户名为 ubuntu
  • cloudflared 提供其他服务的 Tunnel 访问,无需备份
  • 我们打算将 kutt, metube, rsstt, uptime-kuma, zlib-searcher 各自备份到 OneDrive Business 的 backup 目录下

就像这样:

OneDrive Backup

初始化 Repository

当前起始路径是用户 ubuntu 的用户目录

$ pwd
/home/ubuntu

# 将 OneDrive 的 odb-token.json 上传到当前用户目录下
.
├── kutt
├── metube
├── odb-token.json
├── rsstt
├── uptime-kuma
└── zlib-searcher

初始化 kutt

$ cd kutt && \
sudo duplicacy init -e -storage-name odb kutt odb://backup/kutt
# 输入 odb-token.json 路径
Enter the path of the OneDrive token file (downloadable from https://duplicacy.com/one_start):../odb-token.json
Enter storage password for odb://backup/kutt:****************  # 加密密码
Re-enter storage password:****************  # 重复加密密码
/home/ubuntu/kutt will be backed up to odb://backup/kutt with id kutt

初始化 metube

$ cd && \
cd metube && \
sudo duplicacy init -e -storage-name odb metube odb://backup/metube

初始化 rsstt

$ cd && \
cd rsstt && \
sudo duplicacy init -e -storage-name odb rsstt odb://backup/rsstt

初始化 uptime-kuma

$ cd && \
cd uptime-kuma && \
sudo duplicacy init -e -storage-name odb uptime-kuma odb://backup/uptime-kuma

初始化 zlib-searcher

$ cd && \
cd zlib-searcher && \
sudo duplicacy init -e -storage-name odb zlib-searcher odb://backup/zlib-searcher

密码凭据管理

指定 OneDrive Business 访问令牌文件路径和每个 Repository 加密密码

# for kutt repo
$ cd && \
cd kutt && \
sudo cp ../odb-token.json .duplicacy/odb-token.json && \
sudo duplicacy set -key odb_odb_token -value .duplicacy/odb-token.json && \
sudo duplicacy set -key password -value "****************"

# for metube repo
$ cd && \
cd metube && \
sudo cp ../odb-token.json .duplicacy/odb-token.json && \
sudo duplicacy set -key odb_odb_token -value .duplicacy/odb-token.json && \
sudo duplicacy set -key password -value "****************"

# for rsstt repo
$ cd && \
cd rsstt && \
sudo cp ../odb-token.json .duplicacy/odb-token.json && \
sudo duplicacy set -key odb_odb_token -value .duplicacy/odb-token.json && \
sudo duplicacy set -key password -value "****************"

# for uptime-kuma repo
$ cd && \
cd uptime-kuma && \
sudo cp ../odb-token.json .duplicacy/odb-token.json && \
sudo duplicacy set -key odb_odb_token -value .duplicacy/odb-token.json && \
sudo duplicacy set -key password -value "****************"

# for zlib-searcher repo
$ cd && \
cd zlib-searcher && \
sudo cp ../odb-token.json .duplicacy/odb-token.json && \
sudo duplicacy set -key odb_odb_token -value .duplicacy/odb-token.json && \
sudo duplicacy set -key password -value "****************"

创建过滤器

在每个 Repository 创建排除/包含过滤器文件,并测试过滤器规则

设置权限

保护对每个 Repository 配置目录的访问

$ cd && \
cd kutt &&
sudo chmod -R 0700 .duplicacy && \
cd && \
cd metube &&
sudo chmod -R 0700 .duplicacy && \
cd && \
cd rsstt &&
sudo chmod -R 0700 .duplicacy && \
cd && \
cd uptime-kuma &&
sudo chmod -R 0700 .duplicacy && \
cd && \
cd zlib-searcher &&
sudo chmod -R 0700 .duplicacy

脚本任务

# 脚本目录和日志输出目录
$ cd && \
mkdir dup && \
cd dup && \
touch backup.sh && \
mkdir logs

编辑 backup.sh

#!/bin/sh
# backup kutt
cd /home/ubuntu/kutt
echo "`date -Iseconds`" Started backing up $PWD ...
/usr/local/bin/duplicacy -log backup -stats
echo "`date -Iseconds`" Stopped backing up $PWD ...
# backup metube
cd /home/ubuntu/metube
echo "`date -Iseconds`" Started backing up $PWD ...
/usr/local/bin/duplicacy -log backup -stats
echo "`date -Iseconds`" Stopped backing up $PWD ...
# backup rsstt
cd /home/ubuntu/rsstt
echo "`date -Iseconds`" Started backing up $PWD ...
/usr/local/bin/duplicacy -log backup -stats
echo "`date -Iseconds`" Stopped backing up $PWD ...
# backup uptime-kuma
cd /home/ubuntu/uptime-kuma
echo "`date -Iseconds`" Started backing up $PWD ...
/usr/local/bin/duplicacy -log backup -stats
echo "`date -Iseconds`" Stopped backing up $PWD ...
# backup zlib-searcher
cd /home/ubuntu/zlib-searcher
echo "`date -Iseconds`" Started backing up $PWD ...
/usr/local/bin/duplicacy -log backup -stats
echo "`date -Iseconds`" Stopped backing up $PWD ...
# finish backup
# restart cloudflared daemon
systemctl restart cloudflared

赋予脚本可执行权限

$ sudo chmod +x backup.sh

定时任务

这台服务器系统时钟是 UTC 时间,为了方便我们睡觉的时候执行备份,可以在系统时间上加 8 个小时

$ sudo crontab -e

# 编辑 Crontab 任务
# 每天 3 点 33 分执行任务脚本,并将日志以时间格式保存到指定位置 (注意修改路径)
# 3 + 24 - 8 = 19 (服务器时区 UTC,管理员时区 UTC+8)
33 19 * * * cd /home/ubuntu/dup/ && ./backup.sh > /home/ubuntu/dup/logs/backup_`date "+\%Y-\%m-\%d-\%H-\%M"`.log 2>&1

上面的脚本、Cron 表达式、目录都可以根据个人需求自行修改,恢复和定期修剪备份也可以使用类似的方式进行。

Ok, just enjoy it! 🥳

参考信息


  1. 除非 设置 了不通过 Keyring/Keychain 来保护密码凭据 ↩︎

  2. 很可惜,似乎目前还 不支持 KDE Kwallet ↩︎

  3. 日常使用也会有点麻烦 ↩︎

  4. 明文保存,任意访问,不安全 ↩︎

  5. 只会在当前终端会话里有效,如果后面您要设置自动任务的脚本,你需要将 export 导入密码凭据环境变量也写入;或者将环境变量写入 Shell 配置文件,比如 .bashrc, .zshrc… ↩︎

  6. 这里的名称依然参考 Wiki 页面 ,但应该将 DUPLICACY_ 去掉且全部小写,;初始化时指定了 Storage Name 也需要带上,比如本文演示的是 r2 ↩︎