自动备份蚂蚁笔记所有数据到Dropbox
记一次翻车
前些天用服务器下载东西,拖回本地的时候随手开了一个FTP服务器。由于下载比较慢,放在后台让它自己慢慢下载去了。然后就关机回家,完全忘了服务器上有一个临时FTP正在运行的事。
(为了让更多人体会到翻车的乐趣,这里放上只要装了python就能运行的零配置FTP脚本)
- danger_ftp.bash
import os from pyftpdlib.servers import FTPServer from pyftpdlib.handlers import FTPHandler from pyftpdlib.authorizers import DummyAuthorizer address = ("::", 21) # listen on localhost, port 21 handler = FTPHandler authorizer = DummyAuthorizer() authorizer.add_anonymous(os.getcwd(),perm='elradfmwM') handler.authorizer = authorizer ftpd = FTPServer(address,handler) ftpd.serve_forever()
anonymous
、写权限、粗心管理员这下凑齐了,这车岂有不翻之理?两个星期后我想起来这事儿的时候,发现蚂蚁笔记打不开了(中间还有插曲,此处不便细说,否则我不至于两个星期后才发现服务端有问题)。进来一看,FTP能接触的每个目录下都被塞了一个Photo.scr
,所有的HTML
文件都被篡改过……
查了下,知道这貌似是中了利用匿名FTP传播的挖矿病毒。虽然在Linux下Photo.scr
不能执行,删掉就好,但所有的HTML
都不行了,只好重装蚂蚁笔记。重装时我还庆幸着病毒是全自动执行,没有手动搞点恶意动作,比如删库之类的。知道所有服务运行起来,图片还是打不开时,我才发现,蚂蚁笔记所有的附件都没了……
自动定时备份
后来找到一个之前的备份,总算是没啥大的损失,不过浪费了一整个晚上。当时我就把自动备份系统提上了日程。
其实半年前我就把**自动异地备份**提上过日程,后面的情况可想而知。此番若不是[fengbrute兄](https://github.com/fengbrute)的回帖,没准我又要好了伤疤忘了疼了。
几天之后,当时请教车祸善后问题的issue有了回帖。于是我研究了fengbrute兄的自动备份方案,然而我没有群晖或者同类的东西,只能是结合这篇文章,使用这个5K Star的脚本:Dropbox-Uploader,做了一个自动备份蚂蚁笔记数据,然后自动上传到Dropbox的方案出来。
一般个人站点,有效数据有上百兆就不错了,除非你有拿蚂蚁笔记当相册的习惯。因此,备份到Dropbox的话,空间不会是问题。~~实在不够还可以去马云家买扩容服务嘛。~~废话不说,代码如下:
- autoBackup.bash
#!/bin/bash # 一些配置 # Dropbox 目录,根目录 / 是你已经创建的 app 目录 DROPBOX_DIR=/$(date +%Y-%m-%d) MONGO_USER="用户名" MONGO_PASS="密码" MONGO_DB="数据库名" LEANOTE_DATA_FILE_PATH=/path/to/leanote/files/dir #临时目录 TEMP_PATH=/path/to/temp/dir/ DB_DUMP_TEMP_PATH=/path/to/temp/dir/db #可执行文件路径 MONGO_DUMP_EXE=/path/to/mongodb/mongodump DROPBOX_UPLOADER=/root/leanote/autoBackup/dropbox_uploader.sh # 定义备份文件名 DataBakName=DB_$(date +"%Y-%m-%d").tar.gz FileBakName=File_$(date +%Y-%m-%d).tar.gz # Dropbox 里 90 天以上的旧数据可以清除 Old_DROPBOX_DIR=/$(date -d -90day +%Y-%m-%d) # 导出 Mongo 数据库,并压缩 echo -ne "Dump database..." $MONGO_DUMP_EXE -d $MONGO_DB -u $MONGO_USER -p $MONGO_PASS --gzip -o $DB_DUMP_TEMP_PATH tar zcf $TEMP_PATH/$DataBakName $DB_DUMP_TEMP_PATH/$MONGO_DB rm -rf $DB_DUMP_TEMP_PATH/* echo -e "Done" # 备份附件等文件 echo -ne "Backup Leanote files..." tar zcf $TEMP_PATH/$FileBakName $LEANOTE_DATA_FILE_PATH echo -e "Done" # 开始上传到 Dropbox echo -e "Start uploading..." $DROPBOX_UPLOADER upload $TEMP_PATH/$DataBakName $DROPBOX_DIR/$DataBakName $DROPBOX_UPLOADER upload $TEMP_PATH/$FileBakName $DROPBOX_DIR/$WebBakName # 清理 Dropbox 里 90 天前的旧数据,这句可能会失败,没关系,90天以后它就有用了…… $DROPBOX_UPLOADER delete $Old_DROPBOX_DIR/ #清理本地文件 rm -rf $TEMP_PATH/$DataBakName rm -rf $TEMP_PATH/$FileBakName echo -e "All done."
具体步骤:
- 按照
Dropbox-Uploader
的说明,下载之,配置之,直至你可以./dropbox_uploader.sh mkdir lalala
并在你Dropbox里面看到这个新建的lalala
文件夹。阿里云等访问不通的问题,只能是各显神通了…… - 把上面的脚本里配置成自己需要的路径,测试一下,应该直接就看到Dropbox里面刚刚上传的文件了。把文件下载下来打开看看,确认内容和大小正常;
- 定时任务:新建
cron
配置如下。注意路径问题。shell # 14:00 in US is about 03:00 in China 0 14 * * * /path/to/autoBackup/doBackup.sh > /path/to/backupLog.log 2>&1
理论上,如果都没错的话,就应该每天都能看到新的备份了。
小内存VPS上的`oom`的问题
备份/压缩过程中`mongoDB`被`oom`
~~因为穷,~~我是在一个128M的乞丐版OpenVZ VPS上运行这么一套东西。运行上述脚本/定时任务可能遇到的问题是:mongoDB
可能在后面tar
打包或者上传的时候由于占用内存太多(多于一半,高于任何其他进程)被oom kill
掉。
由于mongoDB
会自动做内存缓存,在任何机器上他都会默认吃掉一半以上的内存,而这些内存并不是非用不可的,没有他们也就是多几次硬盘IO
而已。mongoDB
文档上声称这些内存缓存是浮动的,如果有其他程序想吃内存,mongoDB
会主动释放一些给他们用。内里的策略就不清楚了,我估计是隔几秒钟看一眼系统空闲内存,如果实在太少了,就吐出来一点还给系统。这里有一个重要前提是:在这个辗转腾挪的过程中,需要有SWAP
空间暂时顶上,维持程序不被oom
——然而乞丐版OpenVZ显然是不具备SWAP
的,于是系统就从吃内存最大的mongoDB
开始杀了。这个策略是默认内置的,这年头才不会有人为128M没SWAP
的机型专门做优化。
解决方案之一是把mongoDB
做成会自动重启的系统服务,或者在脚本末尾检测mongoDB
还活着没,死了的话再电它一下。然而这终究不是个让人舒坦的解决办法。
于是我查了半天如何限制mongoDB
的内存使用,搜索结果中有一半是解释mongoDB
内置的自动释放机制设计的多么好,小白看见RAM
被占用了不用担心云云,另一小半是在说用cgroup
限制它——且不说再开个cgroup
又要多少内存,即使用了,cgroup
也就是在超限的时候提前kill
、不等系统oom
而已,在小鸡上面用处不大。
后来终于翻到了一个选项:--wiredTigerCacheSizeGB ,此参数可以直接设定内存池占多少GB
,多少GB
,GB
……虽然这个选项的单位制看得我很不好意思,我还是横下心来在配置文件中把它改成了0.039
,也就是大约40M
,重启服务,free -h
一下,果然内存空出来一大半,再执行脚本试验,毫发无伤,皆大欢喜。
上传过程中`mongoDB`与`dropbox_uploader`同归于尽
文件积累变多之后,笔记的files
目录可能增大到上百兆,此时有可能出现的情况是:上传文件的过程中需要大量的内存。由于dropbox_uploader
实际上调用curl
进行网络操作,上传过程中会报错curl
被kill
,然后大文件传不上去。出现这种情况的时候,即使调整过上述mongoDB
选项,它多半也已经被干掉以便腾出空间给curl
了,这就叫同归于尽啊。
dropbox_uploader.sh
文件开头有一个迷惑性相当大的选项:CHUNK_SIZE
,说明指出这是以M
为单位的大文件分块传输时所用的块大小,我把它设置为了5
,按理说再大的的文件也该分割成5M
的小块分别上传,怎么会内存超限?
搜了下github的issue,果然找到有人遇到了这个问题。作者透露,脚本里面还有个隐藏的选项,控制着体积大于某个数值的文件才会启用分块上传机制,而这个默认值是150M
……
这个数值直接写在了if [[ $FILE_SIZE -gt 157286000 ]]; then
一行中,以字节为单位。我将它改成了5242880
也就是5M
,测试一下,完美通过。
PS 1: 开始时我发现备份失败还以为是`tar`打包那一百多兆文件时被搞掉的。这样看来,老牌工具火这么多年果然是有原因的。
> PS 2: 突然想到我之前还用过一个`64M`内存的小鸡,我觉得这种机器就不要挣扎了吧,认命是福……
# 重要修正与补充
2018年02月16日添加
昨天迁移服务器,很幸运或者很悲哀地发现,上面脚本备份的东西是不能用的,原因有两个:
Leanote
的迁移必须整个目录迁移,原因是有一些除files
目录之外的数据也是动态生成的,所以不能只备份files
目录,而应该备份整个leanote
文件夹。这个文件夹是可以直接迁移的。如我之前想象的一样“从官网下载个新的leanote
,然后把files
目录恢复进去就能用”的想法,是错误的。- 在数据逐渐增多时,在超小内存机器上上述脚本会无预警地生成损坏的数据。这一点非常可怕,原因是小内存机器从本地
dump
数据库的时候,高速读取会造成mongo
引擎被OOM
,然后留下的部分损坏的数据被“正常地”压缩、上传。大内存机器上是不会出现这个问题的。目前检查数据是否完整的官方方法不多,我想到的方案是时不时检查数据库进程运行时间是否超过一天,超过了就说明上一次备份没有挂掉。
同时,改进了删除老数据机制,每个月15号不删除老数据。新脚本如下:
- autoBackup.bash
#!/bin/bash # 一些配置 DROPBOX_DIR=/$(date +%Y-%m-%d) # Dropbox 目录,根目录 / 是你已经创建的 app 目录 MONGO_USER="用户名" MONGO_PASS="密码" MONGO_DB="数据库名" LEANOTE_DATA_FILE_PATH=/root/leanote/leanote # command prefix to minimize resource usage #CMD_PREFIX="nice -n19 ionice -c3 nocache" CMD_PREFIX="" #"ionice -c3 nocache" #临时目录 TEMP_PATH=/root/leanote/autoBackup/temp/ DB_DUMP_TEMP_PATH=/root/leanote/autoBackup/temp/db #可执行文件路径 MONGO_DUMP_EXE=/root/leanote/mongodb-3.6.2/mongodump DROPBOX_UPLOADER=/root/leanote/autoBackup/dropbox_uploader.sh # 定义备份文件名 DataBakName=DB_$(date +"%Y-%m-%d").tar.bz2 FileBakName=File_$(date +%Y-%m-%d).tar.bz2 # Dropbox 里 90 天以上的旧数据可以清除 Old_DROPBOX_DIR=/$(date -d -90day +%Y-%m-%d) # 导出 Mongo 数据库,并压缩 echo -ne "Dump database..." $CMD_PREFIX $MONGO_DUMP_EXE -d $MONGO_DB -u $MONGO_USER -p $MONGO_PASS --gzip -o $DB_DUMP_TEMP_PATH $CMD_PREFIX tar -cjf $TEMP_PATH/$DataBakName $DB_DUMP_TEMP_PATH/$MONGO_DB rm -rf $DB_DUMP_TEMP_PATH/* echo -e "Done" # 备份附件等文件 echo -ne "Backup Leanote files..." $CMD_PREFIX tar -cjf $TEMP_PATH/$FileBakName $LEANOTE_DATA_FILE_PATH echo -e "Done" # 开始上传到 Dropbox echo -e "Start uploading..." $CMD_PREFIX $DROPBOX_UPLOADER upload $TEMP_PATH/$DataBakName $DROPBOX_DIR/$DataBakName $CMD_PREFIX $DROPBOX_UPLOADER upload $TEMP_PATH/$FileBakName $DROPBOX_DIR/$WebBakName # 清理 Dropbox 里 90 天前的旧数据 CurrentDate=$(date +%d) if [ "$CurrentDate" -ne "15" ]; then echo -e "Deleting old data..." $CMD_PREFIX $DROPBOX_UPLOADER delete $Old_DROPBOX_DIR/ else echo -e "I won't deleted old data today!" fi #清理本地文件 $CMD_PREFIX rm -rf $TEMP_PATH/* echo -e "All done."