ghost blog 自动备份

分享

ghost-blog 自动备份

blog放在digitalocean上,说不定哪天忘记缴费就给停了,超出时间数据就都没了。

数据

先确定需要备份哪些数据,其实我们所需要备份的数据只有是数据库数据、主题(如果用的是默认数据库,就没有必要了)

$ cd ghost/content
$ du -h 
200K	./data
8.0K	./apps
24K	    ./themes/casper/assets/fonts
12K	    ./themes/casper/assets/js
52K	    ./themes/casper/assets/css
280K	./themes/casper/assets
12K	    ./themes/casper/partials
336K	./themes/casper
24K	    ./themes/casper_ok2/assets/fonts
60K	    ./themes/casper_ok2/assets/js
116K	./themes/casper_ok2/assets/css
392K	./themes/casper_ok2/assets
12K	    ./themes/casper_ok2/partials
444K	./themes/casper_ok2
784K	./themes
8.0K	./images
1004K	.

可以看到我目前blog里面一个content所有内容才1M不到(imags是空的,之前我有blog写过使用反向代理把dropbox作为图床)。  
其中有两个主题,ghost默认主题casper就可以删了,仅备份casper_ok2

rm -rf ./themes/casper
打包

确认环境中已经安装了7z或者zip等压缩工具

//在content目录下
7z a content.7z ./
//或者
zip content.zip ./ -r
上传

我使用七牛的服务来备份数据库,七牛各种语言的官方文档

我使用golang方式,实际推荐qrsync命令行
首先先安装一下依赖库

go get-u qiniupkg.com/api.v7

做个测试,把本地content.7z上传到七牛服务器上,改名为qiniu_content.7z

package main

import (
	"fmt"
	"qiniupkg.com/api.v7/conf"
	"qiniupkg.com/api.v7/kodo"
	"qiniupkg.com/api.v7/kodocli"
)

var (
	//设置上传到的空间
	bucket = "----------"
)

//构造返回值字段
type PutRet struct {
	Hash string `json:"hash"`
	Key  string `json:"key"`
}

func main() {
	//初始化AK,SK
	conf.ACCESS_KEY = "****************************************"
	conf.SECRET_KEY = "++++++++++++++++++++++++++++++++++++++++"

	//创建一个Client
	c := kodo.New(0, nil)

	//设置上传的策略
	policy := &kodo.PutPolicy{
		Scope: bucket,
		//设置Token过期时间
		Expires: 60,
	}
	//生成一个上传token
	token := c.MakeUptoken(policy)

	//构建一个uploader
	zone := 0
	uploader := kodocli.NewUploader(zone, nil)

	var ret PutRet
	//设置上传文件的路径
	filepath := "./content.7z"
	//调用PutFileWithoutKey方式上传,没有设置saveasKey以文件的hash命名
	res := uploader.PutFile(nil, &ret, token, "qiniu_content.7z", filepath, nil)
	//打印返回的信息
	fmt.Println(ret)
	//打印出错信息
	if res != nil {
		fmt.Println("io.Put failed:", res)
		return
	}
}
自动化定时

说到底,这东西还是得手动上传,太麻烦了。
那么来了一个自动化上传的。

time 库

先来测试time 库

//test_time.go
curtime := time.Now()
fmt.Printf("time.Now() is %v\n", curtime)

g1 := curtime.Format("2006-01-02")
g2 := curtime.Format("2008-01-03")
fmt.Printf("Format 2006-01-02, get the result is %v\n", g1)
fmt.Printf("Format 2008-03-12, get the result is %v\n", g2)

g3 := curtime.Format("15:04:05.99999999")
fmt.Printf("Format 15:04:05.99999999, get the result is %v\n", g3)

//stdout
time.Now() is 2016-11-01 17:24:04.516592119 +0800 CST
Format 2006-01-02, get the result is 2016-11-01
Format 2008-03-12, get the result is 1008-11-05
Format 15:04:05.99999999, get the result is 17:24:04.51659211

要想format正确,必须使用2006-01-02 15:04:05这个时间戳,golang的生日,其中含义如下

1 2 3 4 5 6 7
月 日 时 分 秒 年 时 区
exec 库

上传之前肯定需要压缩,需要调用7z或者zip(我这里只写了7z)

//cmd := exec.Command("7z a " + time.Now().Format("2006-01-02") + "_content.7z /home/ch/workspace/go/go_upload/m/") 
cmd := exec.Command("7z", "a", time.Now().Format("2006-01-02")+"_content.7z", "/home/ch/workspace/go/go_upload/m/")
var out bytes.Buffer
cmd.Stdout = &out

err := cmd.Run()

if err != nil {
	log.Fatal(err)
}
fmt.Printf("\n\n\tall logout from exec:-------------------- start ----------------\n")
fmt.Printf("%s\n", out.String())
fmt.Printf("\tall logout from exec:-------------------- end   ----------------\n\n")

注意注释的地方,如果Command里面的参数是整个命令字符串,将会报错
(/home/ch/workspace/go/go_upload/m/实际是存在的)

2016/11/01 17:38:22 fork/exec 7z a 2016-11-01_content.7z /home/ch/workspace/go/go_upload/m/: no such file or directory
exit status 1
定时

代码在文章结束处有下载
目前主函数的逻辑大致如下:

func main() {
	curtime := time.Now()
	filename := curtime.Format("2006-01-02") + "_content.7z"
	cmd := exec.Command("7z", "a", filename, "./apps", "./themes", "./images", "./data")
	err := cmd.Run()

	if nil != err {
		log.Fatal(err)
	}

	uploadToqiniu(filename)
}

现在想办法定时

crotab [-u user] file 或
crotab [-u usre] [-e | -l | -r]

-u user 设置某个用户的crontab服务
file 文件名,载入任务列表
-e 编辑crontab文件内容
-l 显示crontab文件内容
-r 删除crontab文件内容
-i 删除crontab时,需确认

比方这是我以前写的一个备份mongodb的
0 */2 * * * /usr/local/dbbackup/backup.sh 1> /usr/local/dbbackup/log/crontab_mongo_back.file &
0 0 * * * /usr/local/dbbackup/deletebackup.sh 1> /usr/local/dbbackup/log/crontab_mongo_delete.file &

前面5位,可以为0、 *、 */n(n为整数)、 n-m(n,m区间)、n,m(n,m两个时间点) 
分别代表 分钟,小时,号数,月份,星期

例子:每天18 : 00至23 : 00之间每隔30分钟重启smb 
0,30 18-23 * * * /etc/init.d/smb restart
例子:每周六、周日的1 : 10重启smb
10 1 * * 6,0 /etc/init.d/smb restart

逻辑问题

crontab设置如下,

//上面已经将 go程序 生成 可执行 backup
0 */12 * * * /home/chaihelen/backup 1> /home/chainhelen/backup.log &

实际情况是如果代码只是在content目录下,我们手动执行,是没有大问题
一旦设置定时器,执行的路径变成了/home/chainhelen/backup,那么程序里面的相对位置都是有问题的

路径
//比如/home/chainhelen/backup,backup是程序,现在在/home/目录下
//运行程序用了"./chainhelen/backup",那么file = "./chainhelen/backup"
file, _ := exec.LookPath(os.Args[0])
//获取绝对位置wpath = "/home/chainhelen/backup"
cpath, _ := filepath.Abs(file)
//父亲一级的路径,ppath = "/home/chainhelen"
ppath.Dir(cpath)

所以代码最终变成这样

func getExeDir() string {
	file, _ := exec.LookPath(os.Args[0])
	wpath, _ := filepath.Abs(file)
	return path.Dir(wpath)
}

func main() {
	curtime := time.Now()
	filename := curtime.Format("2006-01-02") + "_content.7z"
	absDir := getExeDir()

	absfilename := path.Join(absDir, "./"+filename)
	absapps := path.Join(absDir, "./apps")
	absthemes := path.Join(absDir, "./themes")
	absimages := path.Join(absDir, "./images")
	absdata := path.Join(absDir, "./data")

	cmd := exec.Command("7z", "a", absfilename, absapps, absthemes, absimages, absdata)
	err := cmd.Run()

	if nil != err {
		log.Fatal(err)
	}

	uploadToqiniu(filename, absfilename)

	rmcmd := exec.Command("rm", absfilename)
	rmcmd.Run()
}

代码下载