阅读视图

发现新文章,点击刷新页面。
🔲 ☆

Backrest:自建备份方案的一种可能

前些时间提到,我的群晖 NAS 数据被套件误删了。由于是第三方套件造成的,即便我之前使用的是正版群晖硬件,也不可能就此向官方讨个说法。不过,我仍庆幸群晖有着强大的应用生态,能借助多种工具的协同配合完成重要数据的备份。例如,我通过 Hyper Backup 对 Docker 数据文件夹做了增量备份,又用 CloudSync 将这些备份同步到了云端。当本地数据意外丢失时,这些云端的备份全部得以幸免于难。

可换用飞牛的 fnOS 作为 NAS 系统之后,这套备份组合也随之失效。

fnOS 无论是界面、应用、生态还是移动端 APP ,都很符合我现阶段对一个 NAS 系统的基本需求。唯一美中不足的地方,就是它的备份功能。

作为 NAS 系统,其备份功能仍显不足。即便是迭代至正式版,它支持的备份方式也依旧有限。而且在实际使用过程中,备份到百度网盘的数据还会因为体积太大而经常上传失败。

我本想着,既然现在的 fnOS 备份不方便,说不定后续的更新迭代会优化这一块,要不就先放一边,等下次想起来再说。可谁曾想,飞牛于近日被爆出存在超高危的 0day 漏洞

这可把我吓了一跳。好在我一直通过 VPN 访问家庭网络,从未将 NAS 公开至公网,仔细检查后也没有发现病毒。

但要命的是,虽然该问题在系统更新后被修复(?),但是飞牛官方对此次事件采取的却是近乎冷处理的公关方式:既没有第一时间向用户发出明确的风险预警和紧急防护指引,也未做任何醒目的升级提醒,更是使用「异常访问风险」、「自身代码疏漏」这类模糊表述淡化问题本质,迟迟不肯正面承认此次漏洞产生的严重危害。

软件存在问题在所难免,承认错误及时处理便是,可飞牛对待此类安全问题却是这般敷衍的态度。我开始重新评估自己对该系统安全性的信任边界。这次的攻击者只是利用漏洞将 NAS 当作肉鸡对外攻击,更现实的担忧在于,若未来攻击直接针对数据本身,后果可能远比此次事件严重。我不希望遇上数据被病毒加密勒索的糟心事,不希望暴露 XP,更不想重新整理一遍动画片……

眼下 NAS 不在身边,我没法立刻更换系统,只能先决定即刻采取备份措施,避免后续出现任何数据安全意外。

但离了群晖的 Hyper Backup 和 Cloudsync 的助阵,有什么简单的方法,可以实现定时的增量备份,并将备份数据保存到云端呢?

这就需要请 Backrest 登场了。

关于

在介绍 Backrest 之前,有必要先引入一个几乎被所有备份方案反复提及的原则——「3-2-1 备份原则」。

所谓「3-2-1 原则」,是指在进行文件备份时,需要做到:

  • 3:至少保留 3 份完整的数据副本,防误删。例如:

    • 一份原始数据
    • 一份本地备份(同地 / 同机器)
    • 一份异地备份(云存储 / 异地 NAS)
  • 2:存储在 2 种不同的介质上,防硬盘损坏。例如:

    • 本地磁盘(机械硬盘、固态硬盘、光盘、磁带)
    • 云存储
  • 1:存储 1 份备份在异地,防勒索病毒、极端物理灾害。例如:

    • 异地 NAS
    • 云存储

在合理执行的前提下,可以最大限度降低数据丢失的风险。

可是,即便我们有意愿按「3-2-1 原则」执行备份,却需要面对另一个残酷的现实:如今机械硬盘的价格,已不是常人所能轻松负担的了。

8T机械硬盘
8T机械硬盘

我 2022 年 3 月购买的一块全新国行 8T 西数 HC320 的价格为 995 元。

但是!细心的小伙伴们应该已经注意到了,在「3-2-1 原则」的示例中,几乎每一步都可以将数据备份到「云存储」上。而云存储的价格,远比同等容量的硬盘便宜。

除了网盘,还有 COS、OSS、S3、B2 等一众云存储服务可供选择,价格都比购买实体硬盘要实惠,我们便可以考虑直接将数据备份到一个乃至多个不同的云端,以较低的成本满足「3-2-1 备份原则」的要求。

最终,我决定使用 Restic 作为我备份体系的核心。

Restic 是一款优秀的命令行备份工具,以快速、安全、高效著称。它支持:

  • 增量备份、数据去重和端到端加密,兼顾高效与安全
  • 多种主流存储后端,包括本地磁盘、网络共享目录(SMB/NFS)、远程服务器(SFTP/SSH)及各类对象存储(AWS S3、阿里云OSS、腾讯云COS等)、云存储服务(OpenStack Swift、Azure Blob Storage等)
  • 断点续传,备份中断后再次执行可从断点继续,无需重新开始
  • 任意快照点恢复(可恢复至某一次备份完整状态),也支持文件级精准恢复,恢复速度快且无需复杂配置,通过命令即可完成
  • 提供仓库校验命令,可定期检查备份数据块的完整性、修复损坏或丢失问题
  • 采用不可变仓库结构,备份过程中不修改已存储的历史数据块,避免历史备份损坏

但由于缺乏图形界面与调度管理功能,仍需配合脚本与 cron 实现自动化。对不想折腾这些东西的我来说,使用起来非常痛苦。

Backrest 正是在这一背景下登场的。作为一款专为简化 Restic 备份管理而设计的 Web UI 和编排工具,Backrest 通过直观的图形界面包装了 Restic 的命令行功能,并补齐了任务调度、状态监控与集中管理等关键功能,让复杂的 Restic 备份操作变得简单易行。

借助 Backrest,我们便能使用 Restic 实现一套可以长期稳定运行、也更容易被维护的备份方案。

准备

在正式开始之前,我们需要先思考一个很严肃的问题:

我们应该备份哪些数据?

备份并不是简单地把所有文件原封不动地复制一份。操作系统、软件包,甚至容器和镜像,本质上都属于可重建资源,只要环境还在,花一点时间就能重新部署。真正值得被长期保存的,是那些随着使用不断产生、积累,且一旦丢失就很难恢复的数据。

从实际使用场景来看,以下几类内容通常应当作为备份的重点:

  • 用户数据与业务数据
    例如文档、照片、媒体文件、项目资料,以及服务运行过程中产生的数据文件。这些内容往往具有唯一性,一旦丢失,几乎无法通过其他方式找回。
  • 服务与系统配置
    包括应用配置文件、服务参数、自定义脚本等。这些文件体积不大,却高度个性化。合理备份可以大幅降低系统重建时的时间成本。
  • 容器的持久化数据
    在使用 Docker 等容器化方案时,需要被备份的并不是容器本身,而是 Volume 或挂载目录中的数据。例如数据库文件、上传内容、状态数据等。
  • 高恢复成本数据

    例如游戏资源、音乐等。严格意义上说,这些文件并非完全不可再获取,但一旦涉及到命名规范、目录结构、封面整理和元数据获取,它们的恢复成本就不再只是「重新下载」那么简单。

相对应地,也有一些内容并不适合作为常规备份对象:

  • 操作系统本身
    系统可以重装,软件可以重新安装,将其纳入备份往往只会徒增体积和复杂度。
  • 缓存、临时文件和可再生成数据
    如构建产物、下载缓存、日志缓存等,这类数据即便丢失,恢复成本也相对较低。
  • 镜像、安装包等可从官方渠道重新获取的资源
  • 可下载的影音视频资源

通过这种取舍,将备份的重点从「尽可能多地保存数据」,转变为「只保存真正重要的那一部分」。不仅能有效控制备份体积和成本,也可以让后续的备份与恢复过程更加清晰、可控。

安装

这里以 Docker Compose 安装 Backrest 为例。

首先,需要在宿主机上创建好必要的数据文件夹,用于存放程序数据、配置、缓存和临时文件:

mkdir -p data config cache tmp rclone

随后新建 compose.yml 文件,填入以下内容:

services:
  backrest:
    image: garethgeorge/backrest:latest
    container_name: backrest
    hostname: backrest
    volumes:
    ## 程序数据文件夹
      - ./data:/data
      - ./config:/config
      - ./cache:/cache
      - ./tmp:/tmp
      - ./rclone:/root/.config/rclone # 挂载 rclone 配置(使用 rclone 远程时需要)
    ## 挂载本地备份路径
      - /vol1/1000/media:/vol1/1000/media
      - /volume2/docker:/volume2/docker:ro
    environment:
      - BACKREST_DATA=/data
      - BACKREST_CONFIG=/config/config.json
      - XDG_CACHE_HOME=/cache
      - TMPDIR=/tmp
      - TZ=Asia/Shanghai
    ports:
      - "9898:9898"
    restart: always

有几点需要注意:

  • 为避免后续误操作,建议挂载本地备份路径时直接使用宿主机原始路径
  • 对于只需备份、不需写入的目录,可适当设置只读 ro 权限(如上例 /volume2/docker)。
  • 如果使用远程存储(如 rclone 对接 S3 / COS / B2 等),记得提前在宿主机生成 rclone 配置文件,并挂载到容器内。
  • 端口 9898 用于访问 Backrest Web UI,如果通过反代访问,也可以不映射到宿主机。

设置

访问 IP:9898 启动 Backrest。Backrest 会根据浏览器语言自动切换显示语言,并弹出设置。你需要先为这台机器上启动的 Backrest 服务设置一个唯一的 实例ID,例如 my-nas ,用以区分多个 Backrest 服务。

接着,设置身份验证。如果你的 Backrest 只计划在内网中使用,那么可以不设置用户,并禁用身份验证。但如果是公开的服务,请务必设置好登录用户和高强度密码,并确保未勾选 禁用身份验证 选框,以启用身份验证功能。

鉴于公开服务可能带来的不可预测因素,建议即便是内网,也使用强密码认证身份,并在配置完毕后关闭 Backrest 服务的端口。

仓库

你应当尽可能自行阅读 backrest 官方指南 (英文) 以了解如何配置仓库,或者查看 Restic 文档 (英文) 了解更多有关仓库的信息。不过我也没看过,诶嘿

但本文演示时所使用的 Backrest 1.11.2 版本已原生支持中文界面,因此在实际配置过程中,基本可以做到无需额外查阅文档即可完成操作。

点击左侧的 「添加仓库」 按钮,新建一个 Restic 仓库。该仓库即为备份数据的最终存放位置,后续所有备份任务都可以将其作为统一的备份目的地。

仓库详情

首先,为其指定一个「仓库名称」。该名称仅用于 Backrest 内部识别不同存储库,例如区分本地仓库与云端仓库,本身并不影响实际的存储路径或数据结构。仓库名称创建后无法修改,因此建议在命名时保持清晰与可区分性。

而最关键的 「仓库 URI」,需要严格按照所选存储方式对应的 URI 格式填写。无论是本地路径、S3 对象存储,还是通过 rclone 访问的远程存储,只有格式正确,Backrest 才能成功连接并初始化仓库。

例如:

  • 本地存储库:/vol1/1000/backups/Restic
    使用本地磁盘或 NAS 上的目录作为仓库,适合作为第一层本地备份1
  • S3 / S3 兼容对象存储:s3:my-backup-bucket/Restic
    适用于 AWS S3 以及各类 S3 兼容服务(如 COS、OSS、MinIO 等)。
  • Backblaze B2:b2:my-b2-bucket/Restic
    以 B2 存储桶作为备份仓库,常见于低成本云端备份方案。
  • SFTP:sftp:user@example.com:/data/Restic-repo
    通过 SFTP 将备份数据存放到远程服务器的指定目录。
  • rclone 远程存储:rclone:remote-name:Restic
    通过 rclone 访问远程存储,其中 remote-namerclone config 中定义的远程名称,Restic 为仓库存放的子目录。

「仓库密码」 用于对 Restic 仓库中的所有数据进行加密。Restic 采用端到端加密设计,所有备份内容在写入存储库之前就已经完成加密,云端或远程存储只会保存加密后的数据本身。可以直接使用 Backrest 自动生成一段强度较高的随机密码,通常已满足安全需求。但如果有统一的密码管理策略,也可以自行指定符合习惯的密码。此外,Restic 也支持通过环境变量(如 Restic_PASSWORDRestic_PASSWORD_FILE 等)提供密码,方便在自动化或无交互环境中使用。

需要特别注意的是:该密码无法被找回或重置。一旦密码遗失,即使仓库文件仍然完整存在,也将无法再解密其中的任何数据。因此,应将仓库密码视为与数据本身同等重要,并妥善保存。建议使用密码管理器进行存储。

下方的 「自动解锁」 选项用于在执行清理(forget)和修剪(prune)操作前,自动处理仓库锁定文件。在单实例、单设备使用的情况下通常不会用到,但如果同一个仓库被多个设备或实例同时访问,开启该选项反而可能带来安全隐患,因此默认保持关闭即可。

参数变量

环境变量和命令参数(Environment & Flags)一节,本地仓库或通过 rclone 访问的常见云存储,大多数情况下可以留空。只有在使用对象存储、需要自定义认证方式,或对 Restic 行为有特殊需求时,才需要进行配置。

这里粘贴一段 AI 的见解,不一定准确。你可以在有需要时详尽地向 AI 发问、或阅读官方文档,并在正式将仓库投入使用前大量测试。

环境变量

用于向 Restic 传递环境变量,最常见的用途是:

  • 提供 对象存储的认证信息
    比如 S3、B2、Wasabi、MinIO 等
  • 设置 rclone 的运行环境变量
  • 或者传递一些 Restic 本身支持的高级参数

Backrest 会在执行 Restic 命令时,把这里填写的变量一并注入到运行环境中。常见示例包括:

  • S3 / 兼容对象存储

    • AWS_ACCESS_KEY_ID
    • AWS_SECRET_ACCESS_KEY
    • AWS_DEFAULT_REGION
  • Backblaze B2

    • B2_ACCOUNT_ID
    • B2_ACCOUNT_KEY
  • Restic 通用

    • Restic_PASSWORD(仓库密码,不想写在仓库配置里的话)

另外一个比较实用的小细节是,这里 支持引用父进程中的环境变量,例如:

AWS_SECRET_ACCESS_KEY=${MY_AWS_KEY}

这在 Docker / systemd 环境中非常好用,可以把敏感信息只放在宿主环境里,而不是写死在 Backrest 配置中。

命令参数

对应的是 直接追加到 Restic 命令后的 flags,属于更偏高级/进阶的用法。适合以下场景:

  • 调整 Restic 的性能或行为
  • 解决网络不稳定、对象存储兼容性问题
  • 临时启用调试或特殊特性

常见例子有:

  • 限制并发、降低 IO 压力

    --limit-upload 20480
  • 指定缓存目录

    --cache-dir /data/Restic-cache
  • 某些 S3 兼容存储需要关闭 HTTP/2

    --option s3.disable-http2=true

这些参数 不会影响 Backrest 本身,只会在它调用 Restic 时生效。

定期修剪

日常执行备份时,Restic 的工作机制是:

  • backup:只往仓库里追加新数据块,从不删旧数据
  • snapshots:只是这些数据块的「索引」,方便按时间点恢复
  • forget:删除的是快照记录本身,不会立刻删除底层数据

还需配合 prune 来完成最后的空间回收工作。prune 意为修剪、精简,在 Restic 中,prune 用于删除不再使用的数据块。具体流程可以概括为:

  • 扫描整个仓库
  • 找出 不再被任何快照引用 的数据块
  • 删除这些数据块,并在必要时重新打包剩余数据以优化空间利用率

但由于 prune 需要遍历仓库索引,并对大量数据块进行读写、校验和重组,这一过程相对耗时且对存储 I/O 压力较大,尤其是在仓库规模已经不小、或使用对象存储的情况下更是如此。因此,Restic 官方文档并不建议在每次备份完成后都立刻执行 prune,而是推荐将其作为一项低频的维护任务,定期运行即可——例如每周一次,或每月执行一次清理。既能有效回收空间,又不会对日常备份造成额外负担。

Backrest 自然是将 prune 也做成了一个可视化的定时任务。

你只需要在初始化仓库时,完成三件事:

  1. 设置最大未使用比

    对应 Restic 的 --max-unused 参数,单位是百分比。

    顾名思义,这个数字代表允许仓库中未使用内容所占的最大百分比:

    • 数字越小:仓库残余无用数据块越少,但检索过程会更费时间、IO 占用也更高
    • 数字越大:清理动作更温和、更快,但仓库里会保留更多暂时用不到的数据块

    对个人 NAS 来说,保持默认的 10 一般已经足够。

  2. 选择 Schedule Type(何时执行)

    这决定 prune 自动运行的方式,有以下几种:

    • Disabled:禁用自动执行,只在手动点击「运行」时才会清理
    • Interval (Hours):每隔 N 小时跑一次
    • Interval (Days):每隔 N 天跑一次
    • Cron:使用标准 cron 表达式自定义时间点

    对个人 NAS 来说,更推荐选择 Interval (Days),每 7 天运行一次;或选择 Cron,设置每周/每月指定时间段(比如无人使用 NAS 的凌晨)运行。

  3. Cron Expression 与 Reference Clock

    当选择 Cron 时,会出现:

    • Cron Expression(Cron 表达式):

      • 界面里常见的 0 0 1 * *:表示「每月 1 日 0 点 0 分」
      • 想要「每周日凌晨 3 点」可以写 0 3 * * 0

      表达式可以直接询问 AI 获取。

    • Reference Clock(参考时间):

      • Local:按服务器本地时区
      • UTC:按 UTC
      • Last Run Time:多用于「间隔」调度,基于上一次实际运行的时间继续往后算

综合起来,一个相对通用、比较稳妥的配置是:

  • 最大未使用比:10(默认)
  • Schedule Type:Cron
  • Cron Expression:0 3 * * 0(每周日凌晨 3 点自动清理)
  • Reference Clock:Local

这样,Restic 就会在通常不怎么用机器的时候自动执行 prune,对仓库进行「垃圾回收 + 瘦身」。既不影响日常使用,又能长期把磁盘空间控制在一个相对合理的范围内。

数据验证

这一项对应的是 Restic 的 check 操作,用于验证仓库中数据的完整性

  • 验证数据占比
    这里填写的是一个百分比,用来控制每次检查时抽查多少数据

    • 100%:每次检查都会完整扫描并校验整个仓库,最安全,但耗时和带宽开销最大
    • 较小的数值(如 5%10%):只随机抽查一部分数据块,能在较低成本下发现大多数潜在问题

    对于已经稳定运行的仓库,一般不需要每次都全量校验,定期抽查即可;而在刚迁移仓库、存储后端不太可靠(比如部分对象存储)时,可以适当调高比例。

  • 调度方式(Schedule Type)
    可根据需求设置为 Interval 或 Cron。
  • Reference Clock
    用于指定调度时间的参考基准,也同上一环节的使用方法一致,一般保持默认。

高级设置

在大多数使用场景下,这一部分完全可以保持默认。Backrest 已经为常见环境选择了相对保守、稳定的配置,除非你对系统资源调度或自动化流程有明确需求,否则无需在这里折腾。

这里同样粘贴一段 AI 的见解,仅供参考。

高级设置可以为备份任务指定运行时的 IO 与 CPU 优先级,用于控制 Restic 在系统中的「存在感」。

硬件优先级

  • IO 优先级
    决定备份任务在磁盘读写层面的优先程度。
    默认的 IO_DEFAULT 表示不做额外干预,由操作系统自行调度。
  • CPU 优先级
    决定备份任务在 CPU 调度中的权重。
    CPU_DEFAULT 同样表示使用系统默认策略。

在以下场景中,这些选项才可能派上用场:

  • 备份任务与数据库、转码、下载等高 IO 负载服务共存
  • 希望备份「慢一点跑」,但不影响前台服务响应
  • 服务器性能有限,需要人为限制备份任务的资源占用

否则,保持默认,Restic 本身已经足够克制。

钩子

Hooks 用于在备份生命周期的特定阶段执行自定义脚本,例如:

  • 备份开始前 / 结束后执行脚本
  • 备份成功或失败时发送通知
  • 备份前暂停服务,结束后再恢复服务

典型使用场景包括:

  • 备份数据库前执行 dump
  • 备份完成后通过 Telegram / 邮件推送结果
  • prunecheck 前后执行清理或校验操作

对于只想安稳备份数据的用户来说,这一功能并非必需;但如果你有自动化流程需求,Hooks 提供了非常大的扩展空间。

配置示例

以创建一个本地仓库为例。

  • 仓库详情

    • 仓库名称:local
    • 仓库 URL:/vol1/1000/backup/mikusa
    • 密码:********(自动生成强密码)
    • 自动解锁:关闭(不选中选框)
  • Environment & Flags(参数变量)

    • 默认
  • Prune(定期修剪)

    • 最大未使用比:10(默认)
    • Schedule Type:Cron
    • Cron Expression:0 3 * * 0(每周日凌晨 3 点自动清理)
    • Reference Clock:Local
  • Check(数据校验)

    • 验证数据占比:10(视数据重要程度和体积而定)
    • Schedule Type:Cron
    • Cron Expression:0 2 1 * *(每月 1 日凌晨 2 点校验)
    • Reference Clock:Local
  • Advanced(高级设置)

    • 默认

计划

点击左侧的 「添加调度计划」 按钮,新建一个 Restic 计划。必须先配置好仓库,才能新建调度计划。

计划详情

这里需要注意的事项与创建 Restic 仓库时基本一致。例如,计划名称必须唯一,且创建后无法修改,主要用于区分不同的备份任务。此外,虽然可以直接选择已创建的仓库作为备份目标,但单个计划只能指定一个仓库;若需要同时备份至多个目标,则需分别创建多个计划。

不过,一个仓库是可以供多个备份计划使用的。在 Restic 中,多个备份计划共用一个仓库时,去重是以「数据内容」为单位进行的,而不是以「计划」或「路径」为单位。即使多个计划备份了相同的文件,这些文件的数据块在仓库中也只会存储一份。因此,即使不同计划备份了大量重叠数据,仓库体积也不会线性增长。

我一开始忽视了「数据去重」的含义,于是在同一存储库里按路径创建了很多仓库……

备份范围

你可以参考 Restic 文档 (英文) 了解更多信息。

  • 路径

    至少添加一个要备份的路径,可以是目录,也可以是文件。Restic 会把这些路径当作备份入口,在快照里保留原本的目录结构。路径也可以在界面中直接选择,避免手动输入错误。

  • 排除规则

    用来排除不需要备份的路径或文件模式,语法与 Restic 的 --exclude 相同。可以使用排除规则把缓存、临时文件、日志等剔出去,减小仓库体积、提升备份速度。

    例如:

    • *.tmp :排除所有临时文件
    • /home/*/.cache :排除用户缓存目录
    • /var/log :排除大量滚动日志

    这一栏是区分大小写的,在类 Unix 系统中需要特别注意这类问题。

  • 排除规则(不区分大小写)

    与上面类似,但匹配时不区分大小写,对 Windows 等环境更友好。比如填写 *.log 时,会同时匹配 .log, .LOG 等。

备份调度

视数据的重要程度,可手动、按每时/每天、或 Cron 设置备份频率,基本上与创建仓库 prune 时的原理一致,就不再赘述了。

如果没有特别复杂的需求,比较常见的配置是:

  • Schedule Type:Cron
  • Cron Expression:0 2 * * *(每天凌晨 2 点)
  • Reference Clock:Local
但这块的 UI 是不是有点问题?强迫症受不了。

保留策略

保留策略(Retention Policy)对应的是 Restic 的 forget 规则,用于控制旧快照的保留。

可选项有:

  • By Count:按数量保留,比如只保留最近 N 个快照。

  • By Time Period:按时间维度保留,是最常用、也最直观的一种。

    该模式下每个字段的意思是:

    • Hourly:每小时最多保留多少个快照
    • Daily:每天最多保留多少个快照
    • Weekly:每周最多保留多少个快照
    • Monthly:每月最多保留多少个快照
    • Yearly:每年最多保留多少个快照
    • Latest (Count):无论时间如何,始终至少保留最近 N 个快照

    因此,截图中示例的配置意为:

    • 小时级:最近 24 小时内,每个小时保留 1 个快照
    • 天级:最近 7 天内,每天至少保留 1 个快照
    • 周级:最近 4 周内,每周至少保留 1 个快照
    • 月级:最近 3 个月内,每月至少保留 1 个快照
    • 年级:不额外保留按年的快照
    • 最近:不额外强制保留「最近 N 个快照」,完全按上面的时间分组来决定
  • None:不做自动清理,所有快照都保留。适合短期测试,不推荐长期使用。

应该根据数据重要程度、存储空间和恢复需求调整保留策略,例如保留 7 天的日快照 + 6 个月的月快照就已足够日常使用。如果数据本就不是频繁备份,那么也就没必要设置太精细的策略。

高级设置

计划的「高级设置」也同仓库处一样,没有特殊需求的话可以保持默认

备份参数对应 Restic backup 命令的额外参数,用于调整备份行为或性能,例如:

  • --one-file-system:只备份当前文件系统,避免跨挂载点
  • --exclude-larger-than 500M:跳过超大文件
  • --compression max(Restic 0.16+):提高压缩等级,换取更小体积

脚本(Hooks)则可以在备份生命周期的不同阶段执行自定义脚本或通知,支持多种 hook 触发点(如 before-backup, after-backup, on-error 等),具体用法可以参考官方 hook 文档。例如:

  • 备份前暂停某个服务、锁库
  • 备份后重启服务
  • 发送任务状态通知

现版本的 Backrest 已原生支持 TG 通知,不用自行编写脚本。只需在 Hooks 处选择 Telegram、确定需要通知的环节,填入 Bot Token 和 Chat ID,再参考官方模板设置一下通知文本,就能知道备份有没有正常执行了。

我的模板如下:

📦 备份任务通知

任务名称:  .Task }}
执行时间( {{ .FormatTime .CurTime )
事件类型:  .EventName .Event }}
仓库 ID( {{ .Repo.Id )
计划 ID:  .Plan.Id }}

{{ if .Error -}}
❌ 任务执行失败

错误信息(
{{ .Error )

 else -}}
✅ 任务执行成功

{{ if .SnapshotStats -}}
📊 备份统计

• 新增数据( {{ .FormatSizeBytes .SnapshotStats.DataAdded )
• 处理文件数:  .SnapshotStats.TotalFilesProcessed }}
• 处理数据量( {{ .FormatSizeBytes .SnapshotStats.TotalBytesProcessed )
• 执行耗时: {{ printf "%.1f" .SnapshotStats.TotalDuration }} 秒

{{ end -}}
{{ end }}

From Backrest 自动通知

效果大概是这样:

📦 备份任务通知

任务名称: backup for plan "book"
执行时间: 2026-02-18T23:40:39+08:00
事件类型: snapshot end
仓库 ID: baidupan
计划 ID: book

✅ 任务执行成功

📊 备份统计

• 新增数据: 0.000 B
• 处理文件数: 4865
• 处理数据量: 533.452 GB
• 执行耗时: 13.3 秒

From Backrest 自动通知

栗子

让我再举个更实际的例子:使用 openlist 挂载百度网盘,并用 rclone 连接 openlist 提供的 webdav 服务。

先获取个人用户 UID/GID:

# mikusa @ truenas in ~ [11:45:35]
$ id
uid=1000(mikusa) gid=3000(mikusa)

准备 .env 环境变量文件,置于 compose.yml 同级路径,填入基础的公共变量:

PUID=1000
PGID=3000
TZ=Asia/Shanghai

安装 openlist:

services:

  openlist:
    image: openlistteam/openlist:latest
    container_name: openlist
    user: ${PUID}:${PGID}
    ports:
      - 5244:5244
    volumes:
      - ./openlist:/opt/openlist/data
    environment:
      - UMASK=022
      - TZ=${TZ}
    restart: always

启动后,打开 openlist 添加百度网盘存储,设置挂载路径,如 /baidupan ,并填写刷新令牌。

刷新令牌需在官方的 OpenList Token 获取工具 获取,找到「百度网盘 (OAuth2) 验证登录」并勾选「使用 OpenList 提供的参数」,点击「获取 Token」,将底下的刷新令牌粘贴至 openlist 对应位置即可。

确认当前用户具备 Webdav 相应权限。

在终端中连接 NAS,进入 Backrest 容器内部命令环境:

docker exec -it backrest /bin/sh

使用 rclone config 新建 rclone 配置, 添加 WebDav 存储,所需的信息有:

  • name 名称: openlist
  • url 地址:http://openlist:5244/dav
  • vendor 提供商:other
  • 用户名 user:mikusa
  • password 密码:mikusa

具体流程这里不做演示。

创建完成后,rclone 对应映射文件夹内的 rclone.conf 文件应当有如下内容:

[openlist]
type = webdav
url = http://openlist:5244/dav
vendor = other
user = mikusa
pass = mikusamikusamikusamikusamikusamikusa
密码部分是自动加密的,因此会与真实密码看起来不太一样。

使用 rclone ls openlist:// 测试配置是否有误,如有输出即表示正常。例如:

/ # rclone ls openlist://baidupan/mikusa
8753641238 零售机_游戏性能大横评_2026.mp4
842538736 琉璃的宝石/AICL-4796~7.rar
829036840 琉璃的宝石/VVCL-2788~9.rar

接着,打开 backrest 创建 Restic 仓库。

假设网盘挂载路径为/baidupan,计划备份到 mikusa/Backup/truenas 内,那就在仓库 URI 一栏填入:

rclone:openlist://baidupan/mikusa/Backup/truenas

如果是多个计划使用同一个仓库,可以考虑勾选自动解锁,否则修剪任务可能会失败。

保存仓库、测试连接成功后,即可新建计划任务使用这个仓库。

你可以先备份一些小文件进行测试,并尝试恢复云端备份文件到本地。待确定一切正常后,再执行大批量备份。

由于上传的都是些经过加密的文件块,体积不大。实测备份约 2T 数据,暂未遇到上传限制问题(具体情况可能因账户与网络环境而异)。唯一可能需要担心的,便是宽带运营商是否会因持续大量的上传而限速了。

最后

以上便是我关于 Backrest 的全部心得了。不同于 Restic 只是一个纯粹的命令行工具,Backrest 本质上是一个常驻运行的 Web 服务,拥有完整的 HTTP 端口监听、用户认证与任务调度能力。为了读取所有需要备份的目录,它在 Docker 中往往需要较高权限,甚至直接挂载宿主机的大量路径。这意味着,一旦 Backrest 本身存在安全漏洞——无论是认证绕过、未授权访问,还是远程代码执行——攻击者能够触及的范围,便远不止备份数据本身。

当然,这并不意味着 Backrest 不值得使用。对于大多数家庭 NAS 场景而言,只要运行在内网环境中,做好基本的隔离与访问控制,风险通常是可以接受的。若你对安全边界有更严格的要求,那么直接使用 Restic + cron 的纯命令行方案,会是更简洁、也更克制的选择。

参考

本文在摸索时期参考了少数派对「3-2-1 原则」的《不想被勒索软件毁掉数据,就按照「3-2-1 原则」来备份文件》,刘一树的《服务器备份 - backrest + rclone + oss》,试验成功、再加上官方推出了中文界面后,便大量依赖 ChatGPT、Gemini、Doubao 等一系列人工智能的解释了。

因而本文 AI 含量较高,如有顾虑,酌情阅读。


  1. 第一层备份,是「多层备份架构」中的术语。指的是将数据备份到本地或同一网络中的存储设备,作为最基础的备份层级。这一层的核心特点是:备份速度最快、恢复时间最短、成本最低。它通常是备份计划中最频繁执行的一部分,主要应对日常误操作、硬盘故障、系统崩溃等常见风险。尽管第一层备份提供了较高的恢复速度,但并不具备防范物理灾害、勒索病毒或盗窃的能力,因此需要配合其他层级的备份(如异地备份)来确保数据的长期安全。
🔲 ☆

Debian 13 新机初始化记录

最近购置了一台 1H1G、20G SSD 的虚拟服务器,为了便于使用,需要配置一下基础环境,例如新建用户、密钥登录、Docker 和 Zsh 等等。

年初参考过《Debian Server 初始化设置 SOP》的流程,也整理了一份《飞牛 fnOS 初始化配置记录》,但都不太适合直接套用到这台机子上。部分命令在 Debian 13 上也不再适用。于是,我决定按照自己的需求重新抄写一份,并用这篇笔记为今年的水文画上句号。

抄过来就是我自己的东西了!

系统

本节命令全部使用 root 用户执行。

更新与升级

官方预装的是 Debian 12,系统相当干净,没有多余的东西,因而无需重装系统。既然是新开通的实例,没有任何业务负担,那么可以放心地进行大版本升级。我决定趁现在就把它更新到最新的 Debian13,满足我积攒已久的升级欲望。

首先,升级系统到最新状态:

apt update
apt upgrade -y
apt full-upgrade -y
apt autoremove -y
apt autoclean

接着,将软件源切换到 Debian 13(trixie),使用以下命令一键修改 /etc/apt/sources.list

sed -i 's/bookworm/trixie/g' /etc/apt/sources.list /etc/apt/sources.list.d/*.{list,sources} 2>/dev/null

然后再次升级系统:

apt update
apt upgrade -y
apt full-upgrade -y
apt autoclean
apt autoremove -y

弹出的选框可以全部按默认选择,待更新完毕后,重启系统:

reboot

重启后,验证系统版本:

lsb_release -a

如果显示类似以下信息,说明已经升级成功:

No LSB modules are available.
Distributor ID: Debian
Description:    Debian GNU/Linux 13 (trixie)
Release:        13
Codename:       trixie

本地化配置

设置时区与 NTP:

timedatectl set-timezone Asia/Hong_Kong
timedatectl set-ntp true
timedatectl status

输出类似:

System clock synchronized: yes
NTP service: active

配置语言环境:

sed -i 's/^# *zh_CN.UTF-8 UTF-8/zh_CN.UTF-8 UTF-8/' /etc/locale.gen
sed -i 's/^# *en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen
locale-gen

输出类似:

Generating locales (this might take a while)...
  en_US.UTF-8... done
  zh_CN.UTF-8... done
  en_US.UTF-8... done
Generation complete.

安装常用工具

安装一些基础工具,涵盖编译环境、系统管理、网络下载、终端增强以及文件处理等我可能用得到但又用不到的功能:

apt install -y build-essential sudo vim curl wget ufw autojump git tmux zsh tree zstd zip unzip lsof fastfetch rsync

新建用户

先设置一个新用户的环境变量,例如 mikusa,用于后续复制粘贴快速执行命令:

USERNAME=mikusa

创建该用户,并将其添加到 sudo 用户组:

useradd -m $USERNAME
usermod -aG sudo $USERNAME

为用户设置本地密码(可选,以防特殊情况需要,密码尽量复杂):

passwd $USERNAME

设置该用户执行 sudo 命令时免密:

echo "$USERNAME ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/$USERNAME-nopasswd > /dev/null
chmod 440 /etc/sudoers.d/$USERNAME-nopasswd

为该用户配置密钥登录,我有现成的公钥,所以这里直接设置一个公钥变量

PUBKEY="ssh-ed25519 AAAABBBBBVVVVVVVVVVVVVVVVVVVV"

创建用户密钥文件夹并授权:

mkdir -p /home/$USERNAME/.ssh
chmod 700 /home/$USERNAME/.ssh

导入现有公钥:

echo "$PUBKEY" | sudo tee /home/$USERNAME/.ssh/authorized_keys > /dev/null

修改权限:

chmod 600 /home/$USERNAME/.ssh/authorized_keys
chown -R $USERNAME:$USERNAME /home/$USERNAME/.ssh

安装 Docker

使用官方命令一键安装:

curl -fsSL https://get.docker.com | bash -s docker

安装完毕后,使用以下命令验证 docker 和 docker compose:

docker info
docker compose version

再将个人用户添加到 docker 用户组:

usermod -aG docker $USERNAME
需用户退出当前终端、重新登录后,docker 组权限才会生效。

用户

切换到 mikusa 用户

su - mikusa

才能继续执行下列个人用户相关的配置。

Zsh 配置

使用脚本一键安装 Oh My Zsh :

sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

安装后会询问是否切换 Shell 为 Zsh,按 y 同意。

安装刚需插件:

git clone https://github.com/zsh-users/zsh-autosuggestions.git ~/.oh-my-zsh/custom/plugins/zsh-autosuggestions
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ~/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting

修改主题为 ys

sed -i 's/^ZSH_THEME=".*"/ZSH_THEME="ys"/' ~/.zshrc

配置 Zsh,启用插件:

echo ". /usr/share/autojump/autojump.sh" >> ~/.zshrc
sed -i.bak 's/plugins=(\(.*\))/plugins=(\1 autojump zsh-autosuggestions zsh-syntax-highlighting z extract sudo cp aliases docker docker-compose)/' ~/.zshrc

添加一些自用 docker 别名:

cat <<'EOF' >> ~/.zshrc

# Docker aliases
alias dcu="docker compose up -d --remove-orphans"     ## 启动服务并移除多余容器
alias dcd="docker compose down"                        ## 停止并删除服务容器
alias dcp="docker compose pull"                        ## 拉取镜像
alias dps="docker ps"                                   ## 查看运行中的容器
alias dlogs="docker logs --follow -n 15"               ## 查看容器日志,跟随最新 15 条
alias dclean="docker image prune -a"                   ## 清理未使用镜像
alias acme.sh="docker exec acme.sh acme.sh"            ## 在 acme.sh 容器中执行 acme.sh
alias nginx="docker exec nginx nginx"                  ## 在 nginx 容器中执行 nginx 命令
alias caddy-reload="docker exec -w /etc/caddy caddy sh -c 'caddy fmt --overwrite && caddy reload'"  ## 格式化 Caddyfile 并重载 Caddy
alias caddy="docker exec caddy caddy"                  ## 在 Caddy 容器中执行 caddy 命令

EOF

再重载 Zsh:

source ~/.zshrc

其他

可以安装一个 trash-cli 作回收站用:

sudo apt install -y trash-cli

追加别名:

cat <<'EOF' >> ~/.zshrc

# trash-cli aliases
alias rm='trash-put'        ## trash-put 将文件或目录移入回收站
alias rmclean='trash-empty' ## trash-empty 清空回收站
alias rmrest='trash-restore' ## trash-restore 还原回收站中的文件
alias rmlist='trash-list'   ## trash-list 列出回收站中的文件
alias rmrm='trash-rm'       ## trash-rm 删除回收站中的单个文件

EOF

后续使用 rm 命令,便无须担心误操作删除文件了。

最后

使用新用户通过密钥连接上实例,并确认一切正常后,再修改 SSH 配置,避免因误操作而失联

先修改默认的 SSH 端口。设置一个端口变量,例如:

PORT=22033

一键替换当前端口:

sudo sed -i "s/^#\?Port .*/Port $PORT/" /etc/ssh/sshd_config

验证 /etc/ssh/sshd_config 配置是否有误:

sshd -t

没有报错的话,重启 SSH:

systemctl reload sshd

测试是否已成功绑定到新端口:

ss -tlnp | grep ssh

有类似以下输出,即表示修改完毕:

root@Reze:~# ss -tlnp | grep ssh
LISTEN 0      128          0.0.0.0:22033      0.0.0.0:*    users:(("sshd",pid=733,fd=6))
LISTEN 0      128             [::]:22033         [::]:*    users:(("sshd",pid=733,fd=7))

再考虑禁用 root 登录:

sudo sed -i 's/^#\?PermitRootLogin .*/PermitRootLogin no/' /etc/ssh/sshd_config

也可以禁用密码登录:

sudo sed -i 's/^#\?PasswordAuthentication .*/PasswordAuthentication no/' /etc/ssh/sshd_config

使用以下命令查看是否已成功修改:

sudo sshd -T | grep -E 'permitrootlogin|passwordauthentication'

若输出:

permitrootlogin no
passwordauthentication no

即表示禁止 root 登录,也禁止密码登录生效。

随后重载 SSH:

systemctl reload sshd
使用 reload 而非 restart,可避免因配置错误断开当前连接。

最后,在确认新端口和密钥登录都可用后,再断开当前连接。

参考

🔲 ☆

关于我收集二次元插画这档事

我喜欢收集插图。

小的时候,我会把卡通图画剪下来贴进本子里,做成小手账;再大些有了零花钱,我便攒着买来一本本畅销的画册;等到有了智能手机、接触到广阔的互联网世界,可收藏的东西一下子变得多了起来:从同人插图到概念设定,从商业插画到独立艺术家作品……我乐此不疲地保存这些图片,一张又一张,一个文件夹接着一个文件夹。

这大概是我坚持最久、也最投入的一个爱好。

年轻时的XP
年轻时的XP

可随着时间的推移,收集的作品越来越多,面对电脑中杂乱无章的图库,我才意识到一个问题:该如何为这些收藏进行分类?

分类

相比影音或图书领域有成熟的管理程序和外部数据库可供整理,电子图片往往只是作者在插画网站上的一次更新,或社交平台上的一条动态,本身并没有过于明确的分类依据。我曾考虑使用标签的方式分类,但这么做大概需要一个形似 Yande.re 的图片程序记录标签,并在收集的时候为每张图片打上标签。新增图片尚可如此处理,存量图片就要麻烦得多——以我的技术能力还无法做到自动为每张图片打上标签。而网上搜索到的图片整理软件大多面向专业设计师,对于个人的插画收集而言,又有些大材小用。

况且,在使用 fnOS 相册、immich 这些带 AI 索引功能的相册程序之后,标签可能就没有存在的必要了。想要什么风格(标签)的图片,只需几个形容词简单描述,就可以快速检索到位。

飞牛相册的AI搜索功能
飞牛相册的AI搜索功能

因此,与其折腾复杂的分类系统,不如把精力放在图片本身的保存上。我决定只做最基本的「来源 + 作者」分类:

  1. 从 Pixiv 上下载的图片可以轻松找到作者,因此按作者细分;
  2. Yande、Konachan、Danbooru 等类 Danbooru 图站的图片均为爱好者上传自互联网,可能源自 Pixiv1,也可能是 Twitter,甚至可能是未在网络上公开过的实体画集扫图。没有具体的作者,直接保存在来源文件夹;
  3. Twitter 上右键另存为的图片寻找作者亦较为繁琐,也同 Yande 一样处理。

可是,在准备整理这些图片时,眼前的问题又让我犯了难。

整理

由于没有电脑,最初的收集工作是在手机上完成的,自然没有、也不可能意识到未来会有分类的需求。所有图片都通过 Pixiv APP 保存在固定的文件夹里,再由相册的云服务功能同步到云端。这样做固然方便,更换手机也不怕图片丢失,但缺点同样明显:一旦保存时的命名格式发生变化,就很容易重复保存同一张图片。

例如,Pixiv 官方 APP 当前保存图片的命名格式是 illust_id_save_data.jpg,会生成诸如 illust_112251893_20251123_202020.jpg 这种带没有必要的 illust 前缀和具体下载日期的又臭又长的非常不 Elegant 的文件名;第三方 APP 则可以自行设置不同的命名格式,比如 112251893_p0.jpg 。当同一作品在不同时间、以不同文件名被保存时,就难以仅凭手机相册的时间轴判断是否重复收藏,当时的相册 APP 也缺乏扫描重复图片的功能。

▼ 不同时期的文件命名格式不同,我也是近些年才有所规范。

此外,早期的 Pixiv 还能下载到作者上传的未经去除 Exif 信息的原始图片,且图片格式可能是 jpgpngbmp 2,导致获取的同一作品甚至连文件体积也可能不一致。

所以,我决定先从「去重」这一步入手。针对完全一致的 MD5、相同的文件名,以及相近的文件大小,可以利用重复文件清理工具「Duplicate Cleaner」快速清理掉这部分特征明显的图片。而内容完全一致、只有文件格式或体积有区别的图片,可以在后续通过 immich 的去重功能筛选出来。

利用工具快速去重
利用工具快速去重

移动」方面,由于几乎所有的 Pixiv 图片都包含一串作品 ID,处理起来相对方便。我的思路是利用 PID 拼接链接访问作品页面、解析作者的 UID 后,按 UID 建立作者文件夹移动图片。我把这份工作交给了 AI,编写了脚本批量移动。

▼ 其实最初我是按 UID(NAME) 的格式命名作者文件夹的,但总有作者爱改昵称,于是我又用 AI 糊了个脚本,把文件夹中昵称的部分统统删除了。

至于 PID 异常或是已被删除无法解析链接的作品,可以通过 SauceNao 溯源,再移动到对应文件夹。

最终,仅是 Pixiv 3就整理出了 12172 张(可能)4不重复的图片,这些图片来自 2750 位(可能)5不同的插画师。

总计约30GB
总计约30GB

至此,对存量图片的处理工作算是告一段落。接下来就可以把整理后的图片上传 NAS,并将其挂载到 immich 作为外部图库以便随时浏览和检索。

备份

我的计划是,既然收图的重心正逐渐从手机转移到电脑,而电脑上又有各种现成的工具可以快速从 Pixiv 下载图片(例如:PixivBatchDownloader),那不妨就直接利用这些工具,按自己喜欢的规则生成文件夹和文件名。这样浏览插图时既可以顺手下载,又能通过工具保存下载记录,有效避免了重复下载。

接着,我只需把插图保存在 OneDrive 的同步目录里,就能自动同步到云端,再配置好群晖 DSM 的「CloudSync」套件,就能定时将插图从 OneDrive 拉取更新到 NAS。这样一来,我不仅可以随时在电脑上下载新的插图,还能通过 immich 集中统一管理,可谓一举多得。

唯一的问题是,CloudSync 偶尔会抽风。有时无法及时从云端拉取文件,有时又把我从 immich 中删除的图片同步回来……这些都还算能接受,直到后来出了点小意外。

意外

2025 年初,闲来无事的我准备清理一下 DSM 中无用的数据,决定卸载一个用不到的第三方社群的 Python 套件,并在卸载时并勾选了「清理数据」。万万没想到,这个套件的数据路径竟指向了 NAS 的磁盘根目录,于是它在清理自身残留文件的同时,也顺便删掉了目录下其他的文件。

等我意识到不对劲紧急关机,满满 14T 硬盘的收藏已经被删得只剩下一半。

好在丢的都是些不算特别重要的影视剧和动画,有空再下回来就是。真正让我恼火的是不知何时还会出岔子的 DSM,尽管责任在我,是我自己安装非官方认证的第三方套件导致的。

我本就不太喜欢 DSM,既然事情已经发展到这一步,索性连同 NAS 系统一并更换,试试近期大火的 fnOS。

只是,此时的 fnOS 尚处在测试阶段,还未开发类似 CloudSync 的同步工具,无法直接从 Onedrive 同步文件,原本依赖 OneDrive 的备份方案就这样中道崩殂,插图收藏也因此停滞。我只得另寻其他既能兼顾下载、又能便于同步收藏的方法。

工具

归根结底,我的需求无非就是先获取插图再保存到 NAS,仅此而已。既然终点是 NAS,为何不跳过这些弯弯绕绕,直接一步到位?在互联网上寻找了一番,我决定使用 Nazurin 实现这一思路。

Nazurin 是一个基于 Telegram 的图片收藏工具,支持从 各种网站 浏览和下载图片。在配置好 Telegram bot 和目标 存储源 后,只需给 TG 机器人发送图片源链接,便可直接将图片保存至目的地。

其实前面的内容都是为了引出 Nazurin 的废话,写着写着写跑题了……

使用

在以往的存图流程中,遇到需要保存的图片后,我必须:

浏览 图片 ➡️ 下载 图片 ➡️ 上传 NAS

中间省略了可能需要执行的分类和去重工作。

而在使用 Nazurin、将存储目的地直接设置为 NAS 本地后、并配置好各图片源的存储规则后,这个流程就被简化成了:

浏览 图片 ➡️ 分享(发送) 源链接至 TG 机器人

只需两步,图片便直接被保存到 NAS 中了,可谓一劳永逸!

而在换用 Nazurin 后,我仍可以在 PC 端保持原有的浏览习惯,像平常一样寻找插图,遇到喜欢的图片一键保存。仅仅只是「更换」了一个「下载」按钮:

不仅自动下载了图片,还为这张图片标记了 ❤
不仅自动下载了图片,还为这张图片标记了 ❤

也可以在移动设备上,将图片的源链接分享给 TG 机器人:

这或许相比直接使用 APP 一键保存略微曲折了些,但至少我不用担心图片的分类问题,也不用再烦恼如何把它们备份到 NAS 了。

按来源分类
按来源分类

按作者细分
按作者细分

另外,通过订阅各图站的 RSS 服务,我还可以在繁忙的时候第一时间在 TG 内获取时下热门图片,再择一收入囊中。代价是插图收集也因此失去了些许个性。

安装

虽然官方演示是搭建在 Fly.io 上,但我有 NAS,所以直接部署在本地就好了!

安装 Nazurin 非常简单,使用 docker compose 即可一键部署:

services:
  nazurin:
    image: yyoung01/nazurin:latest
    container_name: nazurin
    user: 1000:1001 # 实测需指定用户运行
    volumes:
      - ./data:/app/data
      - /vol1/1000/photos/Nazurin:/Nazurin # 替换为你的存储路径
    # ports:
    #   - 8080:8080
    environment:
     # ---------- 必填项 ----------
      - TOKEN=123456 # 替换为你的 Telegram 机器人 Token
      - ENV=development
     # - WEBHOOK_URL=http://127.0.0.1:8080
     # - HOST=0.0.0.0
     # - PORT=8080
      - STORAGE=Local,Telegram
      - DATABASE=Local
      - ADMIN_ID=123456 # 替换为你的 Telegram 用户 ID
      - ALBUM_ID=-10012345 # 替换为你的 Telegram 频道 ID
      - STORAGE_DIR=/Nazurin # 需与挂载的内部存储路径一致
     # ---------- 可选项 ----------
      - TZ=Asia/Shanghai
      - IS_PUBLIC=false
      - RETRIES=8
      - MAX_PARALLEL_DOWNLOAD=6
      - HTTP_PROXY=http://mihomo:7890 # 替换为你的代理地址
      - HTTPS_PROXY=http://mihomo:7890 # 替换为你的代理地址
      - CLEANUP_INTERVAL=7
      - FEEDBACK_TYPE=reply
      - BILIBILI_FILE_PATH=Bilibili/{user[mid]}
      - PIXIV_TOKEN=123456 # 替换为你的 Pixiv Token
      - PIXIV_TRANSLATION=zh-CN
      - PIXIV_FILE_PATH=Pixiv/{user[id]}
      - PIXIV_FILE_NAME={filename}
      - TWITTER_AUTH_TOKEN=123456 # 替换为你的 Bearer Token
      - TWITTER_FILE_PATH=Twitter/{user[id_str]}
    restart: always

这里与官方提供的 docker-compose.yml 不同的是,官方指定了一个 env_file 配置,里面包含了完整的环境变量,即便绝大多数都可以保持默认,但数量仍多得吓人。所以我稍微精简了下,仅保留了部分自定义的内容。

在这个配置的环境变量中,可选项自定义的功能如下:

  • 同时存储于本地和 Telegram
  • 私有 bot
  • 最大并行下载数量为 6
  • 使用代理。官方文档只写了需要 HTTP_PROXY ,但其实 HTTPS_PROXY 也是必须的
  • 反馈方式设置为在原消息上添加表情回应
  • 可下载 Pixiv、Twitter、Bilibili 图片至指定作者文件夹

    • Pixiv 下载路径设置为 Pixiv/{user[id]},会在 Pixiv 文件夹内建立 作者 ID 文件夹;文件名格式设置为纯数字 ID
    • Twitter 下载路径设置为 Twitter/{user[id_str]} ,会在 Twitter 文件夹内建立 作者的 用户id 文件夹,例如 1507705239507329034。你可以使用这个链接 https://x.com/intent/user?user_id={user_id} 一键跳转到对应用户,如:https://x.com/intent/user?user_id=1507705239507329034

      • 如果你更喜欢使用 @用户名 文件夹,例如 https://x.com/xinzoruo 里的 xinzoruo ,那么可以使用 {user[screen_name]} 来设置作者文件夹 6
    • Bilibili 下载路径设置为 Bilibili/{user[mid]} ,会在 Bilibili 文件夹内建立作者的 uid 文件夹
  • 临时目录设置为 7 天后自动清理

可以具体参考下节的配置说明。

配置

这里详细说明几处需要额外注意的配置。

  • user: 实测需指定用户运行,懒得找可以直接使用 1000:1001,否则登录 SSH 后使用 id 获取你自己的 id

    $ id
    uid=1000(mikusa) gid=1001(Users) groups=1001(Users),994(docker),1000(Administrators)
  • TOKEN:机器人的 API 密钥,可从 @BotFather 获取
  • ADMIN_ID:管理员用户的 Telegram 用户 ID,可从 @userinfobot 获取
  • ALBUM_ID:用于存储图片的频道 ID,可通过网页 TG Web 获取,格式为 -100xxxxxx
  • Env:如果你需要在网页端快捷分享图片至 TG,需要将运行环境设置为 production ,即 Webhook 模式,并指定挂载端口,才能搭配官方的 Nazurin 浏览器扩展 一键分享。否则,使用 development 模式,让机器人轮询访问 TG 即可。
  • 关于 Webhook 模式,官方的说明是:

    发送到 Telegram 服务器的 Webhook URL,机器人的服务器应能通过此 URL 访问,应以 / 结尾,例如 https://xxx.fly.dev/

    也就是说,这个 URL 需要被公开,且必须能被 Telegram 服务器访问,才能正常使用

    这也意味着如果 Nazurin 是运行在家里的 NAS 上,为了正常使用官方的 浏览器扩展必须将 Nazurin 反代至公网

  • HTTP_PROXY:代理,需要一并填写 HTTPS_PROXY ,否则无法正常下载 Pixiv 图片
  • FEEDBACK_TYPE:收藏图片成功后的反馈方式,可选值如下:

    • reply:回复原消息
    • reaction: 在原消息上添加表情回应
    • both:回复并添加表情回应
      回复原消息
      回复原消息
  • PIXIV_TOKEN:可以使用第三方 APP 快捷获取,例如 Pixez。登录后在账户信息页可以一键导出 Token

如果你没有太多图站需要收集,那么便无需使用完整的 .env 文件,只需在环境变量中填写基本的 TG 配置,外加一条 Pixiv Token,就足够启动了。

当然,如果你想要更精细些的分类、更多的图站支持,或者是其它配置,例如外部数据库、云端存储、自定义其他图站的保存路径,就需要自行详细阅读官方文档后,再额外添加了。

完整的环境变量配置如下,注释部分已使用 AI 进行翻译:

[details sum="点击展开完整环境变量"]

# 这是一个 .env 配置文件示例
# 更多信息请参阅:https://nazurin.readthedocs.io/getting-started/configuration/
# 修改数值并取消相应行的注释后,将文件重命名为 .env

# ---------- 必填项 ----------
# Telegram Bot 的令牌(token)
# TOKEN =

# 运行环境
# production: Webhook 模式;development: 轮询(Polling)模式
ENV = production

# Webhook 地址,例如:https://xxx.fly.dev/,必须以 '/' 结尾
# 使用 Webhook 模式时必填
# WEBHOOK_URL =

# 监听的主机地址,如果使用反向代理请设为 127.0.0.1
# 使用 Webhook 模式时必填
# HOST = 0.0.0.0

# 监听端口,如果部署在 Heroku 或 fly.io 上请注释掉本行
# 使用 Webhook 模式时必填
# PORT =

# 存储类型,用逗号分隔
STORAGE = Local

# 数据库类型
DATABASE = Local

# Telegram 图库频道 ID,可选(官方文档写的是 GALLERY_ID,这是个过时的变量,这里已经更改)
# ALBUM_ID =

# 管理员用户 ID
# ADMIN_ID =

# ---------- 可选项 ----------
# 存储目录路径
# STORAGE_DIR = Pictures

# 是否将此 Bot 设置为公开
# IS_PUBLIC = false

# 如果 IS_PUBLIC 为 True,则以下配置项将被忽略
# 允许的用户 ID(可多个)
# ALLOW_ID =

# 允许的用户名(可多个)
# ALLOW_USERNAME =

# 允许的群组 ID(可多个)
# ALLOW_GROUP =

# 重试次数
# RETRIES = 5

# 请求超时时间
# TIMEOUT = 20

# 下载文件时写入的分块大小(字节)
# DOWNLOAD_CHUNK_SIZE = 4096

# 最大并行下载数量
# MAX_PARALLEL_DOWNLOAD = 5

# 最大并行上传数量
# MAX_PARALLEL_UPLOAD = 5

# 网络请求的代理 URL,默认为系统环境设置
# HTTP_PROXY = http://127.0.0.1:7890

# 在图像说明(caption)中忽略的内容
# CAPTION_IGNORE =

# 临时目录清理间隔(天)
# CLEANUP_INTERVAL = 7

# 日志等级,参考:https://docs.python.org/3/howto/logging.html#logging-levels
# LOG_LEVEL = INFO

# 收藏图片成功后的反馈方式
# FEEDBACK_TYPE = reply

# ----- Google 服务 -----
# Firebase & Google Drive 的 API 凭证
# GOOGLE_APPLICATION_CREDENTIALS =

# ---------- 网站相关 ----------
# ----- Artstation -----
# 文件目录
# ARTSTATION_FILE_PATH = Artstation

# 文件名
# ARTSTATION_FILE_NAME = {title} ({hash_id}) - {filename}

# ----- Bilibili -----
# 文件目录
# BILIBILI_FILE_PATH = Bilibili

# 文件名
# BILIBILI_FILE_NAME = {id_str}_{index} - {user[name]}({user[mid]})

# ----- Bluesky -----
# 文件目录
# BLUESKY_FILE_PATH = Bluesky

# 文件名
# BLUESKY_FILE_NAME = {rkey}_{index} - {user[display_name]}({user[handle]})

# ----- Danbooru -----
# 文件目录
# DANBOORU_FILE_PATH = Danbooru

# 文件名
# DANBOORU_FILE_NAME = {id} - {filename}

# ----- DeviantArt -----
# 文件目录
# DEVIANT_ART_FILE_PATH = DeviantArt

# 文件名
# DEVIANT_ART_FILE_NAME = {title} - {deviationId}

# 下载文件的命名
# DEVIANT_ART_DOWNLOAD_NAME = {title} - {deviationId} - {prettyName}

# ----- Gelbooru -----
# 文件目录
# GELBOORU_FILE_PATH = Gelbooru

# 文件名
# GELBOORU_FILE_NAME = {id}

# ----- Kemono -----
# 文件目录
# KEMONO_FILE_PATH = Kemono

# 文件名
# KEMONO_FILE_NAME = {pretty_name}

# ----- Lofter -----
# 文件目录
# LOFTER_FILE_PATH = Lofter

# 文件名
# LOFTER_FILE_NAME = {id}_{index} - {nickName}({blogName})

# ----- Moebooru -----
# 文件目录
# {site_name} -> Yandere,{site_url} -> 'yande.re'
# MOEBOORU_FILE_PATH = {site_name}

# 文件名
# MOEBOORU_FILE_NAME = {filename}

# ----- Pixiv -----
# Refresh Token
# PIXIV_TOKEN =

# 图片镜像,可选
# PIXIV_MIRROR = i.pximg.net

# 标签翻译,可选
# PIXIV_TRANSLATION =

# 收藏隐私(public/private),可选
# PIXIV_BOOKMARK_PRIVACY = public

# 文件目录
# PIXIV_FILE_PATH = Pixiv

# 文件名
# PIXIV_FILE_NAME = {filename} - {title} - {user[name]}({user[id]})

# ----- Twitter -----
# API 选择,可选
# TWITTER_API = web

# Web API 的 Auth Token,可选
# TWITTER_AUTH_TOKEN =

# 文件目录
# TWITTER_FILE_PATH = Twitter

# 文件名
# TWITTER_FILE_NAME = {id_str}_{index} - {user[name]}({user[id_str]})

# ----- Wallhaven -----
# API Key,可选
# WALLHAVEN_API_KEY =

# 文件目录
# WALLHAVEN_FILE_PATH = Wallhaven

# 文件名
# WALLHAVEN_FILE_NAME = {id}

# ----- Weibo -----
# 文件目录
# WEIBO_FILE_PATH = Weibo

# 文件名
# WEIBO_FILE_NAME = {mid}_{index} - {user[screen_name]}({user[id]})

# ----- Zerochan -----
# 文件目录
# ZEROCHAN_FILE_PATH = Zerochan

# 文件名
# ZEROCHAN_FILE_NAME = {id} - {name}

# ---------- 数据库 ----------
# ----- MongoDB -----
# MONGO_URI = mongodb://localhost:27017/nazurin

# ----- Cloudant -----
# CLOUDANT_USER =
# CLOUDANT_APIKEY =
# CLOUDANT_DB = nazurin

# ---------- 存储 ----------
# ----- Telegram -----
# 图库频道 ID
# ALBUM_ID =

# ----- MEGA -----
# MEGA_USER =
# MEGA_PASS =

# ----- Google Drive -----
# 文件夹 ID
# GD_FOLDER =

# ----- OneDrive -----
# 应用(客户端)ID
# OD_CLIENT =

# Refresh Token
# OD_RF_TOKEN =

# 客户端密钥
# OD_SECRET =

# ----- S3 -----
# 终端节点(Endpoint)
# S3_ENDPOINT = s3.amazonaws.com

# Access Key
# S3_ACCESS_KEY =

# Secret Key
# S3_SECRET_KEY =

# 是否使用 SSL
# S3_SECURE = True

# 区域
# S3_REGION =

# Bucket 名称
# S3_BUCKET = nazurin

[/details]

其他

到这里,这篇文章就算彻底结束了。本来只是想简单写写 Nazurin 的用法,结果在开头卡了很久……于是就写成这样了。

总之,如果你同我一样有收集插图的爱好,且拥有一台 NAS,或是一台小服务器,希望能随时随地地保存那些美好的图片,那么 Nazurin 会是个很不错的小助手。

参考


  1. 如果是源自 Pixiv 的图片,我也会优先从 Pixiv 下载,除非这些图片已经被作者从 Pixiv 上删除。所以 Yande 上下载的图片一般不会出现与 Pixiv 重复的情况。
  2. bmp 格式不确定是不是 Pixiv 自带的,只是我整理时发现有保存这个格式的图片。
  3. Pixiv 以外的图片,由于下载时有注意区分,几乎不用处理。
  4. 毕竟数量巨大,难免会有漏网之鱼,还需未来进一步整理。
  5. 存在有些画师账号被封另起炉灶、或是起了个小号偷偷画涩图的情况。
  6. 理论上,将 Twitter 的作者文件夹设置为 id_str 最为稳妥,这串数字是固定不变且唯一的,但 screen_name 又更加便于搜索,只需与 https://x.com/ 拼接即可。你可以按需求选择设置。赌的就是作者不会轻易更换这个 id
❌