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()
}

代码下载