前两天看到 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
用户执行,那其实文件夹读写之类根本就不会有权限问题,也就是上面这步其实可以完全省略。总之这回终于可以成功运行了,欧耶。