PostgreSQL 踩坑 & Vercel 搭建 Cusdis 心得

背景

自建博客数据统计服务 umami v2 中我曾提到要迁移博客的评论系统,日历转到八月,看了眼 Railway 不思悔改心意已决,便注销账号(顺带放弃我珍贵的 $3.7 余额)。然鹅天有不测风云,小半个月过去 Vercel 提供外包的数据库在 dashboard 仍旧没有读写记录,umami 后台也一直显示 0 访客。

博客虽然冷冷清清凄凄惨惨戚戚,但也不至于完全没有流量,我百思不得其解,加之之前访客的 referrer 大都来自 cn.bing.com,还以为禁用了 GPTBot 影响如此严重。机缘巧合下才发现原来是 vercel.json 配置错误,如有兴趣详见 #Vercel 部署 Umami,此处不再赘述。

正巧前几日在 GitHub Trending 看到 sql-mother,经过一晚上的学习算是对 SQLite 的基础功能都掌握了七七八八,随后也速读了『SQL 必知必会』和『MySQL 必知必会』,『SQL 进阶教程』也搬上了日程,也就摩拳擦掌想手动实践一番。奈何 cusdis 年久失修(其实是在准备 V2 大版本更新),虽然偶有更新,但依赖版本已经大幅落后,而且存在巨量安全漏洞,实际部署之前少不了一番 PR。闲话到此为止,下面开始回顾搭建 PostgreSQL 数据库踩的各种坑。

Vercel 搭建 Cusdis

由于 PostgreSQL 的坑实在是多,先介绍一下相对简单的这部分。理论上参考 官方文档,fork 仓库后填写对应参数即可,但事与愿违,Vercel 免费方案只能托管一个 PostgreSQL 数据库,已经用作博客数据统计,评论系统就只能自己动手丰衣足食了。

这里先假设 PostgreSQL 的坑已经填完,Vercel 能够顺利连接数据库,那么应该会出现这么一个错误:

精简后的日志Failed to compile. ./style.css.webpack[javascript/auto]!=!./node_modules/next/dist/build/webpack/loaders/css-loader/src/index.js??ruleSet[1].rules[1].oneOf[13].use[1]!./node_modules/next/dist/build/webpack/loaders/postcss-loader/src/index.js??ruleSet[1].rules[1].oneOf[13].use[2]!./style.css Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './lib/node' is not defined by "exports" in /vercel/path0/node_modules/postcss/package.json

Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath ‘./lib/node’ is not defined by “exports” in /vercel/path0/node_modules/postcss/package.json

– inner error – Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath ‘./lib/node’ is not defined by “exports” in /vercel/path0/node_modules/postcss/package.json

根据 这个 issue,升级 postcss 依赖或设置 Node 版本为 16.x 即可解决。 记得成功后到 Vercel 环境变量页面添加 NEXTAUTH_URL 为自定义域名再重新部署。 可以顺便设置 NEXT_TELEMETRY_DISABLED=1 关闭 NextJS 遥测数据的收集

之后参考 Hugo 论坛教程 完成 Hugo 的 Cusdis 配置即可,如果遇到跨域问题可以参考 Sometimes form shows on page, sometimes not - CORS issue

附上脱敏后的 Hugo 配置参考,我干了几天也没有什么别的,大概两件事:

  1. 根据文章的 isCJKLanguage 值选择性显示翻译挂件
  2. 打完安全补丁后部署访问 cusdis.es.js 返回 404,根据 Vercel 输出的静态文件目录,经过和 https://cusdis.com/js/cusdis.es.js 对比确认改为 cusdis.umd.js 可以正常工作
<!-- layouts/_default/single.html -->
<div id="cusdis_thread"
  data-host="https://log.vinfall.com"
  data-app-id="xxxxxx"
  data-page-id="{{ .File.UniqueID }}"
  data-page-url="{{ .Permalink }}"
  data-page-title="{{ .Title }}"
>
</div>
{{ if .Params.isCJKLanguage }}
<script defer src="https://log.vinfall.com/js/widget/lang/zh-cn.js"></script>
{{ end }}
<script async defer src="https://log.vinfall.com/js/cusdis.umd.js"></script>

很惭愧,就做了一点微小的工作。

PostgreSQL 数据库搭建的最佳实践

安装

倒也算不上坑,只是一开始一顿操作,做完 best practice,结果发现 PostgreSQL 15 修改了公共模式的默认行为,导致前功尽弃。1 我使用的是 Debian testing/unstable,可以选择从 Debian 官方源安装,也可以选择 PostgreSQL 提供的 Apt 源。由于前面提到的 15 大版本问题,我两种都尝试过,让我意外的是,PostgreSQL 官方源 竟然还支持 Debian testing/13 和 unstable。建议 stable 用户选择 Debian 官方源,而 testing/unstable 用户选择 PostgreSQL 源。

访问 IP 设置

由于架构上后端数据库只要求 Vercel 能够访问,最好禁止除了数据库本机 IP、局域网和 Vercel IP 之外的所有访问。随手一搜在 networksdb.io 还真搜到了 Vercel 的 IP 范围76.76.21.0 - 76.76.21.255),注意 PostgreSQL 配置中的 IP 均为 CIDR 掩码,只填 IP 地址是没有用的……

这部分都是现学现卖的,就不班门弄斧了,参考下面两个页面自行设置:

SSL

配置 PostgreSQL 的 SSL 访问需要三个证书相关文件:

  • server.key: 密钥
  • server.crt: 服务器证书
  • root.crt: 根证书

同上一节,具体流程自行参考以下页面和 PostgreSQL 官方文档(写得挺好的):

连接测试

# restart
sudo systemctl enable postgresql
sudo systemctl restart postgresql
sudo systemctl status postgresql

# test
su -- postgres
psql -U <user-name> -d <db-name>
psql -U <user-name> -h <配置的数据库域名> -d <db-name>
psql "postgresql://$DB_USER:$DB_PWD@$DB_SERVER/$DB_NAME"

参考配置

脱敏后的配置节选,仅供参考:

/etc/postgresql/15/main/pg_hba.conf

# If you want to allow non-local connections, you need to add more
# "host" records.  In that case you will also need to make PostgreSQL
# listen on a non-local interface via the listen_addresses
# configuration parameter, or via the -i or -h command line switches.
host  db-name  user-name   76.76.21.0/24   md5
hostssl  db-name  user-name   76.76.21.0/24   md5
#host  db-name  user-name   76.76.21.0/24   trust
#hostssl  db-name  user-name   76.76.21.0/24   trust
host db-name  user-name   127.0.0.1/32   md5
hostssl db-name  user-name   127.0.0.1/32   md5
# 注意末尾的 /0,漏了就会报错
host db-name  user-name   <本机 IP>/0   md5
hostssl db-name  user-name   <本机 IP>/0   md5

# Database administrative login by Unix domain socket
# 由于 postgres 用户的鉴权基于 Unix,这里不能改为 md5,否则 log 会一堆报错
local   all             postgres                                peer

# "local" is for Unix domain socket connections only
# 这里从 peer 改成 md5,否则无法使用 psql 用户的密码访问
local   all             all                                     md5

/etc/postgresql/15/main/postgresql.conf

#listen_addresses = 'localhost'         # what IP address(es) to listen on;
listen_addresses = 'localhost,<本机 IP>,<数据库配置的域名>'
#listen_addresses = '*'

ssl = on
ssl_ca_file = '/path/to/root.crt'
ssl_cert_file = '/path/to/server.crt'
#ssl_cert_file = '/etc/ssl/certs/ssl-cert-snakeoil.pem'
#ssl_crl_file = ''
#ssl_crl_dir = ''
ssl_key_file = '/path/to/server.key'
#ssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key'
ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers
ssl_prefer_server_ciphers = on

各种坑

Postgres createuser command not found after installing with homebrew on my Mac

createuser 命令不在 $PATH,加上就好了。参考命令和输出如下,于是乎把 /usr/lib/postgresql/14/bin 加到 $PATH

$ sudo find / -name createuser
/usr/lib/postgresql/14/bin/createuser
/usr/bin/createuser
/usr/share/bash-completion/completions/createuser

PostgreSQL: Why psql can’t connect to server?

症状:安装过 Debian 官方源的 postgresql 卸载并且未清除配置,随后安装 PostgreSQL 提供的 Apt 源,用 postgresql 用户创建用户提示 Is the server running locally and accepting connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"?,反正就是服务没启动连不上。

解决方案:查看日志发现 postgresql-14 的端口被改成了 5433,在对应配置文件中改回 5432,重启服务,一切正常。

$ vim /var/log/postgresql/postgresql-14-main.log
LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5433"
$ VISUAL=vim sudoedit /etc/postgresql/14/main/postgresql.conf
port = 5432

Can’t Connect to Postgresql on Port 5432

考虑了端口、防火墙、加密方式、权限等等原因,重启服务无数次,最后发现还是 pg_hba.conf 的问题,格式为 TYPE DATABASE USER CIDR-ADDRESS METHOD,其中 IP 地址用标准的带有 CIDR 掩码长度的点分十进制声明,掩码长度表示客户端 IP 地址必须匹配的高位二进制位数。在给出的 IP 地址里,这个长度的右边的二进制位应该为零。在 IP 地址、/、CIDR 掩码长度之间不能有空白。要声明单个主机,给 IPv4 地址声明 CIDR 掩码 32,给 IPv6 地址声明 128。不要在地址中省略结尾的 0。

就是被这个 CIDR 坑了,折腾了一小时……

结语

虽然洋洋洒洒两千字,但实际上踩坑部分也只鼓捣了一个半小时,第一天基于 PostgreSQL 15 的最佳实践尝试也只花了一晚上,和更新 Cusdis 那点过时依赖等待 Vercel 和 dependabot 一遍又一遍检测依赖更新并部署到 preview 浪费的五个小时相比,根本不值一提。

这次又要表演一次那个:不要靠近 JavaScript,会变得不幸?不要靠近 JavaScript,会变得不幸。不要靠近 JavaScript,会变得不幸!


  1. 其实并没有,但这点我要到第二天踩了许多坑顺利搭建完 PostgreSQL 14 才发现 ↩︎

Vinfall's Geekademy

Sine īrā et studiō