给站点设置数据库自动备份

前两天看到 Firefish 又更新了小版本,这次换了一个全文搜索引擎。鉴于站点搜索表现一直很差,经常需要十几秒或者就干脆报错,于是蠢蠢欲动打算更新。转念一想发现本站虽然有硬盘 snapshot 备份,但数据库没有被包含在内,所以决定先设置一个可以自动备份的流程,peace of mind。然后痛苦就开始了。

Attempt 1

上星期在 Github 上写了个小程序,于是就想到能不能薅 Github 的羊毛来进行备份(毕竟相当于免费储存)。

首先:新建一个私人仓库,配置 SSH key,初始化文件夹。到这一步都还算顺利,除了一开始把链接设置成了 HTTPS 的(从 Linux 上传当然用 SSH 更方便,不如说我没搞懂怎么用 HTTPS)。接下来让 ChatGPT 给我写了一个可以自动创建备份文件并自动 push 到 Github 的脚本(自行添加了测试代码):

#!/bin/bash

# Setting the date format for the filename
DATE=$(date +%Y-%m-%d_%H-%M-%S)

# Path where the backup will be saved
BACKUP_DIR="/path/to/backup/directory"

# Database name to backup
DB_NAME="your_database"

# Username to connect to the PostgreSQL database
USERNAME="postgres"

# Perform the backup
sudo -u $USERNAME pg_dump $DB_NAME > "$BACKUP_DIR/db_backup_$DATE.sql"
# Test code
# touch $DATE.txt

# Check if the dump was successful
if [ $? -eq 0 ]; then
  echo "Backup was successful. Proceeding with Git commit..."

  # Navigate to the backup directory
  cd "$BACKUP_DIR"

  # Test code
  # git add "$DATE.txt"

  # Git operations
  git add "db_backup_$DATE.sql"
  git commit -m "Database backup for $DATE"
  git push origin main

else
  echo "Backup failed. No changes committed to Git."
fi

命名为 pg_backup.sh。保存后记得运行 chmod +x pg_backup.sh 使之成为可被运行的脚本。

这个脚本可以实现每次只上传新增的备份文件,而不影响其他文件。E.g. 如果我之后在本地删除已完成上传的文件,不会影响到 Github 上面保存的文件。用空白 txt 文件时测试毫无问题,然而运行正式代码时就报错:

remote: error: File db_backup_xx.sql is 2180.22 MB; this exceeds GitHub's file size limit of 100.00 MB
remote: error: GH001: Large files detected. You may want to try Git Large File Storage - https://git-lfs.github.com.
To github.com:username/backup.git

好,文件太大是吧,那我就来设置 git lfs……结果废了老鼻子劲,折腾好了,报错:

[xxxxxxxxxxxxxxxxxxxx] Size must be less than or equal to 2147483648: [422] Size must be less than or equal to 2147483648
error: failed to push some refs to 'github.com:username/backup.git'

啊?.jpg 原来你只能接受 2GB 以下的文件啊!白折腾了!!怒卸载 git lfs,删库!

Attempt 2

发现 Github 这条路走不通,苦思冥想,如果想免费好像只有商业网盘可以用用。于是我再次寻求 ChatGPT 老师的帮助,老师言:你用 rclone 吧!看了看支持的网盘列表,心想用 Google Drive 好了,免费 15GB,还挺多的,能装七个备份文件吧。又开始吭哧吭哧装包,装完发现:不对啊,这玩意儿授权要在浏览器进行,我用 VPS 当然没有图形界面可用。于是进行一通搜索看有没有什么方法设置 Google Drive,然后看到这篇帖子,发现 Google Drive 的授权很有可能每隔一段时间要重新设置,无法实现真正的全自动化。我迅速打起退堂鼓,放弃这条路,还得卸载 rclone……

Attempt 3

至此我对于薅免费羊毛的想法已经被各种失败的尝试打败了,还是老老实实上 object storage 吧,多花点钱也就认了。刚好月初清理了站点的媒体缓存并直接关掉了缓存,桶大小一下从 155GB 骤降到 500MB,半个多月过去桶大小增长地非常缓慢,毕竟本站活跃用户本来就非常少。就算同时保存 10 份备份,也不会超过 25GB,要付的钱还不如之前缓存媒体的时候多。好在 VPS 上 aws 的 config 早在设置媒体的时候就已经搞好了,不需要再琢磨配置。我又双叒叕转向 ChatGPT 老师:帮我写一下 S3 上传文件的命令吧!老师完美地完成了任务,修改后的脚本如下:

#!/bin/bash

# Setting the date format for the filename
DATE=$(date +%Y-%m-%d_%H-%M-%S)

# Path where the backup will be saved
BACKUP_DIR="/home/database-backup"

# Database name to backup
DB_NAME="firefish"

# Username to connect to the PostgreSQL database
USERNAME="postgres"

# Perform the backup
cd $BACKUP_DIR
sudo -u $USERNAME pg_dump $DB_NAME > "$BACKUP_DIR/db_backup_$DATE.sql"
# Test code
# touch $DATE.txt

# Check if the dump was successful
if [ $? -eq 0 ]; then
  ls
  echo "Backup was successful. Proceeding with uploading to Scaleway..."

  aws s3 cp /home/database-backup/db_backup_$DATE.sql s3://user1/backup/db_backup_$DATE.sql --storage-class ONEZONE_IA --acl private --endpoint-url https://s3.your.endpoint
  aws s3api put-object-tagging --bucket user1 --key backup/db_backup_$DATE.sql --tagging '{"TagSet": [{ "Key": "type", "Value": "backup" }]}' --endpoint-url https://s3.your.endpoint

# Test code
# aws s3 cp /home/database-backup/$DATE.txt s3://user1/backup/$DATE.txt --storage-class ONEZONE_IA --acl private
# aws s3api put-object-tagging --bucket user1 --key backup/$DATE.txt --tagging '{"TagSet": [{ "Key": "type", "Value": "backup" }]}'

  echo "Successfully uploaded backup file and set the tag."

# rm $DATE.txt
  rm db_backup_$DATE.sql
  ls
  echo "Successfully deleted uploaded file."

else
  echo "Backup failed. No file is uploaded."
fi

这次添加了上传成功后删除备份文件的命令以节省 VPS 硬盘空间,还添加了额外的 ls 来列出当前目录下文件,更直观地确认操作无误。同时在上传时设置好储存级别及可见度,并添加 tag 来进行后续的 lifecycle 管理。这回脚本完美地运行了测试代码,没有任何报错,真是非常令人感动。

不过一开始踩了一个坑。原本的备份路径在我的用户 home 路径下:/home/user1/database-backup,导致换回实际备份代码时总是会报这样的错:

could not change directory to "/home/user1/database-backup": Permission denied

原因是 postgres 用户对我当前用户 user1 目录下的任何文件夹都没有写入权限。于是把备份的目录换到了 home 目录下(/home/database-backup),并授予我的用户和 postgres 用户读写权限:

sudo setfacl -m u:user1:rwx /home/database-backup
sudo setfacl -m u:postgres:rwx /home/database-backup

现在再运行的时候就不会报错了。终于来到了最后一步,可以设置定时运行了!crontab -e 后添加:

0 7 * * 1 /home/database-backup/pg_backup.sh > /home/database-backup/log 2>&1

后半部分是 ChatGPT 老师帮我添加的 log 选项,便于后续查看及 debug。这个脚本将在每周一的早上七点运行,因为 VPS 和我不处于一个时区,所以是我的凌晨三点。写到这里突然想起,其实为了数据完整性,最推荐的做法应当是停止 Firefish 服务后再进行备份,但我实在是懒得弄了,就这样吧(心虚)

最后在 Scaleway 设置了所有备份文件在两周后被转为 archive,60 天后删除。我想应该是足够多的备份文件了,虽然前提是这个备份文件没有出任何问题,可以用于恢复数据库……至于更新版本的事情,还是改天再说吧。下班!

更新

还是踩了别的坑。首先:如果 crontab -e,设定的程序是以当前用户身份执行。然而因为这个脚本里含有 sudo 命令,实际运行时需要输入密码,定时运行会报错。有两个解决方法,1:在脚本里直接写入密码,这当然极度极度不推荐,非常危险;2:设置时执行 sudo crontab -e,使用 root 用户执行。然而我的 root 用户没有配置过 aws,所以上传的时候出错了。整了半天也没把 endpoint 设置对,用了好几种方法,包括生成新的配置文件(没有成功,怀疑一台机器只能生成一个配置文件??),使用全局变量(命令不对,放弃寻找别的 reference),所以最后只好使用 --endpoint-url 参数把 url 直接写到脚本里。话又说回来,如果使用 root 用户执行,那其实文件夹读写之类根本就不会有权限问题,也就是上面这步其实可以完全省略。总之这回终于可以成功运行了,欧耶。

留下评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注