delphij's Chaos

选择chaos这个词是因为~~实在很难找到一个更合适的词来形容这儿了……

29 May 2024

记一笔 paperless-ngx

其实想搭一套 paperless-ngx 已经挺久的了, 趁着四月底休假的时候搞了一动。

我们经常会有一些各式各样的 PDF 文档,例如和各种供应商、公司之间签署的文件、账单, 以及参加一些学术会议时留下的幻灯、资料等等。比较自然的办法就是创建不同的文件夹来分门别类地保存。 然而,简单地用文件系统来保存这些文件有这样一些问题:

  1. 文件系统的首要目标是实现按名字(路径)存取文件。现实生活中,文档往往可能会和不同的事件关联。例如,同一份收入证明可能同时用于贷款申请、签证两件无关的事上,维持多份副本浪费空间,而维持一份副本采用硬连接或符号连接则存在兼容性方面的疑虑。
  2. 对一个懒人来说,从网络上下载的 PDF 文件可能来不及及时整理,而等到积累到一个比较大的 Downloads 目录的时候,理性的选择往往是把所有文件放到 PENDING_DELETE_<TODAY+365days> 目录中,然后等到期之后世界整体删除。
  3. 扫描的纯图片 PDF 可能不太容易检索。许多商业产品,包括 Google Drive,提供了 OCR 功能,但这意味着需要把文件传到别人那里明文存放,这可能有隐私和安全方面的疑虑。
  4. 占用本地空间。如果不介意包含比较大的存储的计算机,这在平时并不是太大的问题,但这会给备份和恢复带来一定的困难。由于笔记本通常并没有足够的冗余,我倾向于让它们做「瘦」客户端,这样如果笔记本坏掉的话我可以选择不还原备份而从头开始。
  5. PDF格式本身是在持续演化的。除非一直使用同一个供应商的PDF实现,否则未来很可能会有一天出现 PDF 打开时与预期不符的情况。理想状态下,我希望有一份经过处理的 PDF/A 副本来确保我能够访问某个下限范围内的文件内容,并同时保存原始的 PDF 文件。
  6. 需要额外的全文检索能力。

之前在上家公司搞的是 NAS,因此我平时也经常会看到一些与此相关的讨论。我发现 paperless 这个项目实现了大部分我希望的功能。 后来,这个项目发展成了 paperless-ng 和 paperless-ngx

FreeBSD 提供了一个 paperless-ng 的 port,不过还需要稍微配置一下。

$ sudo pkg install py311-paperless-ngx redis nginx ca_root_nss

首先是 PostgreSQL 数据库,基本上,创建一个 paperless 的数据库,以及一个专门的用户来访问该数据库:

$ su -l postgres
$ createuser paperless
$ createdb paperless -O paperless
$ sql paperless_db
alter role paperless with encrypted password '修改此密码'
grant all privileges on database paperless to paperless
exit

启用 redis:

$ sudo sysrc redis_enable=yes

port 提供了一个示范的 /usr/local/etc/paperless.conf。除了数据库、redis之外我主要修改了下面的配置:

  • PAPERLESS_SECRET_KEY。这个随机生成一个长串即可(我不太理解为什么系统不自动生成一个存下来)。
  • PAPERLESS_URLPAPERLESS_CSRF_TRUSTED_ORIGINSPAPERLESS_ALLOWED_HOSTSPAPERLESS_CORS_ALLOWED_HOSTS。填写 BeyondCorp 的地址。
  • PAPERLESS_OCR_LANGUAGE。我设置了 eng+chi_sim+chi_tra(英语、简体中文、繁体中文)
  • PAPERLESS_OCR_USER_ARGS。我设置了 {"invalidate_digital_signatures": true}。有些 PDF 文件有数字签名,此时 OCRmyPDF 会拒绝识别文字,这是没有必要的,原因是 paperless-ngx 已经保留了原始文件。
  • PAPERLESS_WORKER_TIMEOUT。我设置了一个比较大的值,原因是机器毕竟已经十几年前的配置了,而且这是个批作业操作,没必要那么早放弃。
  • 去掉了分类器的 OpenBLAS 的 OpenMP 支持。后来 发现 其他人用了一些更加简单粗暴的解法。如果不这么做的话,分类器的 celery 会吃到 100% 的 CPU,具体现象是会不停地调用 sched_yield(),而 OpenBLAS 拒绝承认这是他们的问题。实际使用中,我认为对个人用户来说,采用多线程对性能的改善有限(分类基本上是个批作业的活,而所有的计算中比较耗时的是 OCR,然而即使是 OCR 在我的机器上也不算很慢了)。

port 还提供了一个示范的 nginx.conf。我做的主要变动是:

client_max_body_size 256m;          # 允许上传不超过 256MB 的 PDF。默认值是 1MB
set_real_ip_from PROXY_IP;          # 相信来自 PROXY_IP 的 X-Forwarded-For IP 地址。
real_ip_header X-Forwarded-For;

/etc/rc.conf 中最终应该有下列启动项目:

postgresql_login_class="postgres"
postgresql_enable="yes"
redis_enable="yes"
paperless_migrate_enable="YES"
paperless_beat_enable="YES"
paperless_consumer_enable="YES"
paperless_webui_enable="YES"
paperless_worker_enable="YES"
nginx_enable="YES"

我启用了 paperless_migrate_enable,主要是因为该服务器有自动快照,而我认为没有理由不让 Django 在软件升级时不做数据库迁移。

单独做一个 paperless-ngx 用于保存数据的 ZFS (默认配置中,在 /var/db/paperless 下) ,可以方便备份。 不过,由于 paperless-ngx 的许多信息是保存在数据库中的,因此实践上还应把数据库等等放在一起才比较符合备份的原则。 我最终选择了对整个 jail 做一个 ZFS 数据集。