# 自动备份蚂蚁笔记所有数据到Dropbox
# 记一次翻车
前些天用服务器下载东西,拖回本地的时候随手开了一个FTP服务器。由于下载比较慢,放在后台让它自己慢慢下载去了。然后就关机回家,完全忘了服务器上有一个临时FTP正在运行的事。
(为了让更多人体会到翻车的乐趣,这里放上**只要装了python就能运行的零配置FTP脚本**)
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`都不行了,只好重装蚂蚁笔记。重装时我还庆幸着病毒是全自动执行,没有手动搞点恶意动作,比如删库之类的。知道所有服务运行起来,图片还是打不开时,我才发现,[蚂蚁笔记所有的附件都没了……](https://github.com/leanote/leanote/issues/689)
# 自动定时备份
后来找到一个之前的备份,总算是没啥大的损失,不过浪费了一整个晚上。当时我就把自动备份系统提上了日程。
> 其实半年前我就把**自动异地备份**提上过日程,后面的情况可想而知。此番若不是[fengbrute兄](https://github.com/fengbrute)的回帖,没准我又要好了伤疤忘了疼了。
几天之后,当时请教车祸善后问题的issue有了回帖。于是我研究了[fengbrute兄的自动备份方案](http://peri-nas.top:216/blog/post/new-pig/%E9%98%BF%E9%87%8C%E4%BA%91%E5%A4%87%E4%BB%BD%E6%95%B0%E6%8D%AE%E5%90%8C%E6%AD%A5),然而我没有群晖或者同类的东西,只能是结合[这篇文章](https://www.lovelucy.info/backup-website-and-sync-to-dropbox.html),使用[这个5K Star的脚本:Dropbox-Uploader](https://github.com/andreafabrizi/Dropbox-Uploader),做了一个自动备份蚂蚁笔记数据,然后自动上传到Dropbox的方案出来。
一般个人站点,有效数据有上百兆就不错了,除非你有拿蚂蚁笔记当相册的习惯。因此,备份到Dropbox的话,空间不会是问题。~~实在不够还可以去马云家买扩容服务嘛。~~废话不说,代码如下:
#!/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."
具体步骤:
1. 按照`Dropbox-Uploader`的说明,下载之,配置之,直至你可以`./dropbox_uploader.sh mkdir lalala`并在你Dropbox里面看到这个新建的`lalala`文件夹。阿里云等访问不通的问题,只能是各显神通了……
2. 把上面的脚本里配置成自己需要的路径,测试一下,应该直接就看到Dropbox里面刚刚上传的文件了。把文件下载下来打开看看,确认内容和大小正常;
3. 定时任务:新建`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的[乞丐版](http://finance.sina.com.cn/chanjing/gsnews/2017-11-20/doc-ifynwxum7560107.shtml)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 ](https://docs.mongodb.com/manual/reference/program/mongod/#cmdoption-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,果然找到有人遇到了这个问题。[作者透露](https://github.com/andreafabrizi/Dropbox-Uploader/issues/363),脚本里面还有个隐藏的选项,控制着**体积大于某个数值的文件**才会启用分块上传机制,而这个默认值是`150M`……
这个数值直接写在了`if [[ $FILE_SIZE -gt 157286000 ]]; then`一行中,以字节为单位。我将它改成了`5242880`也就是`5M`,测试一下,完美通过。
> PS 1: 开始时我发现备份失败还以为是`tar`打包那一百多兆文件时被搞掉的。这样看来,老牌工具火这么多年果然是有原因的。
> PS 2: 突然想到我之前还用过一个`64M`内存的小鸡,我觉得这种机器就不要挣扎了吧,认命是福……
# 重要修正与补充
> 2018年02月16日添加
昨天迁移服务器,很幸运或者很悲哀地发现,上面脚本备份的东西是不能用的,原因有两个:
1. `Leanote`的迁移必须整个目录迁移,原因是有一些除`files`目录之外的数据也是动态生成的,所以不能只备份`files`目录,而应该备份整个`leanote`文件夹。这个文件夹是可以直接迁移的。如我之前想象的一样“从官网下载个新的`leanote`,然后把`files`目录恢复进去就能用”的想法,是错误的。
2. 在数据逐渐增多时,在超小内存机器上上述脚本会**无预警地生成损坏的数据**。这一点非常可怕,原因是小内存机器从本地`dump`数据库的时候,高速读取会造成`mongo`引擎被`OOM`,然后留下的部分损坏的数据被“正常地”压缩、上传。大内存机器上是不会出现这个问题的。目前检查数据是否完整的官方方法不多,我想到的方案是时不时检查数据库进程运行时间是否超过一天,超过了就说明上一次备份没有挂掉。
同时,改进了删除老数据机制,每个月15号不删除老数据。新脚本如下:
#!/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."