Commit b2e6741f authored by abbycin's avatar abbycin

windows can also run as serivce now

parent 7d728720
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
配置使用TOML格式,示例如下: 配置使用TOML格式,示例如下:
```toml ```toml
[[PGConfig]] [[PGConfig]]
pg_home = "C:/Program Files/PostgreSQL/10/bin" # pg 安装目录
host = "127.0.0.1" host = "127.0.0.1"
port = 5432 port = 5432
db = ["test", "postgres"] db = ["test", "postgres"]
...@@ -24,32 +25,31 @@ username = "admin" ...@@ -24,32 +25,31 @@ username = "admin"
password = "admin" password = "admin"
destination = "/data/backup" destination = "/data/backup"
keep = 10 keep = 10
cron = "0 0 1 * * *" # 每天1点0分0秒运行 cron = "* 1 * * *" # 每天1点0分0秒运行
options = ["--inserts"] options = ["--inserts"]
``` ```
配置中`keep`表示保留最近 N 天, `cron`表示何时执行备份任务,6个`*` 分别表示: 秒, 分, 时, 本月第几天,月,本月第几天,举例如下: 配置中`keep`表示保留最近 N 小时, `cron`表示何时执行备份任务,5个`*` 分别表示: 分, 时, 本月第几天,月,本周第几天,举例如下:
- `33 1 * * * *` 每个小时的 1 分 33 秒执行 - `*/1 * * * *` 每1分钟执行
- `*/1 * * * * *` 每秒执行 - `0 */1 * * *` 每小1时执行
- `0 */1 * * * * ` 每分钟执行 - `0 0 */1 * * ` 每月1号执行
- `23 30 1 * * *` 每天 1 时 30 分 23 秒执行 - `23 2 1 * *` 每月1号2时23分执行
更多参考 [这里](https://godoc.org/github.com/robfig/cron) 更多参考 [这里](https://godoc.org/github.com/robfig/cron)
## 用法 ## 用法
1, Windows/Linux下直接运行 1, Windows/Linux下直接运行
2, Linux下可选择安装为服务 `./dbackup -service install` (以root用户运行) 2, 可选择安装为服务 `./dbackup -service install` (Linux下以root用户运行)
可用参数有 `-service start`, `-service stop`, `-service restart`, `-service install`, `-service uninstall` 可用参数有 `-service start`, `-service stop`, `-service restart`, `-service install`, `-service uninstall`
程序会读取程序所在目录下的`conf.toml`配置,并在当前目录下创建`log`目录用于记录日志 程序会读取程序所在目录下的`conf.toml`配置,并在当前目录下创建`log`目录用于记录日志
## 注意 ## 注意
1,必须保证`pg_dump` 在环境变量中 1, 如果可以找到`pg_dump`那们配置中的`pg_home`可以设置为空(即pg_home = "")
2, 程序只会使用必要的参数用于连接数据库,额外的参数可以通过`options`传递 2, 程序只会使用必要的参数用于连接数据库,额外的参数可以通过`options`传递
3, 输出备份文件名为 `db_yyyymmdd-HHMMSS.dump` 且不可配置,存放位置为 `destination/db/db_yyyymmdd-HHMMSS.dump` 3, 输出备份文件名为 `db_yyyymmdd-HHMMSS.dump` 且不可配置,存放位置为 `destination/db/db_yyyymmdd-HHMMSS.dump`
3, windows中程序不能作为服务运行(因为pg_dump找不到pgpass)
## 示例 ## 示例
......
[[PGConfig]] [[PGConfig]]
pg_home = "C:/Program Files/PostgreSQL/10/bin" # where postgresql installed
host = "127.0.0.1" host = "127.0.0.1"
port = 5432 port = 5432
db = ["test", "postgres"] db = ["test", "postgres"]
username = "abby" username = "abby"
password = "abby" password = "abby"
destination = "D:/backup" destination = "D:/backup"
keep = 10 keep = 10 # keep latest 10 hours dump, set -1 to disable cleanup
cron = "0 30 1 * * *" # run every day at 01:30:00 cron = "1 * * * *" # run every 1 minute
options = ["-Fc", "--no-owner"] options = ["-Fc", "--no-owner"]
#[[PGConfig]] #[[PGConfig]]
...@@ -17,5 +18,5 @@ options = ["-Fc", "--no-owner"] ...@@ -17,5 +18,5 @@ options = ["-Fc", "--no-owner"]
#password = "admin" #password = "admin"
#destination = "/data/backup" #destination = "/data/backup"
#keep = 10 #keep = 10
#cron = "0 0 1 * * *" # run every day at 01:00:00 #cron = "0 1 * * *" # run every day at 01:00:00
#options = ["--inserts"] #options = ["--inserts"]
\ No newline at end of file
...@@ -3,7 +3,7 @@ package detail ...@@ -3,7 +3,7 @@ package detail
import ( import (
"dbackup/logging" "dbackup/logging"
"github.com/kardianos/service" "github.com/kardianos/service"
"github.com/robfig/cron" "github.com/robfig/cron/v3"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
...@@ -26,21 +26,20 @@ func InitLogger() { ...@@ -26,21 +26,20 @@ func InitLogger() {
} }
e = log.Init(&log.Logger{ e = log.Init(&log.Logger{
FileName: curr, FileName: curr,
RollSize: 1 << 30, RollSize: 1 << 30,
RollInterval: time.Hour * 30, RollInterval: time.Hour * 30,
Level: log.DEBUG, Level: log.DEBUG,
PanicOnFatal: true, PanicOnFatal: true,
}) })
} }
type Job interface { type Job interface {
AddTask(*Task) error AddTask(*Task) error
} }
type Task struct { type Task struct {
Ch chan struct{} Ch chan struct{}
Cron *cron.Cron Cron *cron.Cron
} }
...@@ -50,10 +49,10 @@ func (t *Task) Start(srv service.Service) error { ...@@ -50,10 +49,10 @@ func (t *Task) Start(srv service.Service) error {
return nil return nil
} }
func (t* Task) Run() { func (t *Task) Run() {
log.Info("running...") log.Info("running...")
t.Cron.Start() t.Cron.Start()
<- t.Ch <-t.Ch
log.Info("stopped") log.Info("stopped")
t.Cron.Stop() t.Cron.Stop()
...@@ -68,4 +67,4 @@ func (t *Task) Stop(srv service.Service) error { ...@@ -68,4 +67,4 @@ func (t *Task) Stop(srv service.Service) error {
func (t *Task) AddTask(task Job) error { func (t *Task) AddTask(task Job) error {
return task.AddTask(t) return task.AddTask(t)
} }
\ No newline at end of file
package detail package detail
import ( import (
"bufio"
log "dbackup/logging" log "dbackup/logging"
"fmt" "fmt"
"github.com/robfig/cron" "github.com/robfig/cron/v3"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
user2 "os/user"
"path/filepath"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
...@@ -22,13 +18,14 @@ const ( ...@@ -22,13 +18,14 @@ const (
) )
type PGConfig struct { type PGConfig struct {
PgHome string `toml:"pg_home"`
Host string `toml:"host"` Host string `toml:"host"`
Port int `toml:"port"` Port int `toml:"port"`
DB []string `toml:"db"` DB []string `toml:"db"`
Username string `toml:"username"` Username string `toml:"username"`
Password string `toml:"password"` Password string `toml:"password"`
Destination string `toml:"destination"` Destination string `toml:"destination"`
Keep int64 `toml:"keep"` Keep int64 `toml:"keep"`
Cron string `toml:"cron"` Cron string `toml:"cron"`
Options []string `toml:"options"` Options []string `toml:"options"`
} }
...@@ -43,85 +40,11 @@ func (x *Postgres) Setup() { ...@@ -43,85 +40,11 @@ func (x *Postgres) Setup() {
log.Fatal("invalid destination: %s, must use Unix Path Seperator `/`") log.Fatal("invalid destination: %s, must use Unix Path Seperator `/`")
} }
for _, i := range c.DB { for _, i := range c.DB {
if e := os.MkdirAll(c.Destination + "/" + i, os.ModePerm); e != nil { if e := os.MkdirAll(c.Destination+"/"+i, os.ModePerm); e != nil {
log.Fatal("can't mkdir: %s, err: %s", c.Destination, e) log.Fatal("can't mkdir: %s, err: %s", c.Destination, e)
} }
} }
} }
user, e := user2.Current()
if e != nil {
log.Fatal("can't get current user: %s", e)
}
var pgpass string
if runtime.GOOS == "windows" {
pgpass = filepath.Join(os.Getenv("AppData"), "postgresql")
_ = os.MkdirAll(pgpass, os.ModePerm)
pgpass = filepath.Join(pgpass, "pgpass.conf")
} else if runtime.GOOS == "linux" {
pgpass = filepath.Join(user.HomeDir, ".pgpass")
} else {
log.Fatal("un-support os")
}
f, e := os.OpenFile(pgpass, os.O_RDWR, 0)
if e != nil {
f, e = os.Create(pgpass)
if e != nil {
log.Fatal("can't create %s, err: %s", f, e)
}
if runtime.GOOS == "linux" {
_ = os.Chmod(pgpass, 0600)
}
}
set := make(map[string]bool)
scanner := bufio.NewScanner(f)
for scanner.Scan() {
ff := scanner.Text()
log.Info("==>> %s", ff)
set[ff] = true
}
if err := scanner.Err(); err != nil {
f.Close()
log.Fatal("can't read pgpass: %s", err)
}
for _, cfg := range x.PGConfig {
for _, db := range cfg.DB {
line := fmt.Sprintf("%s:%d:%s:%s:%s", cfg.Host, cfg.Port, db, cfg.Username, cfg.Password)
set[line] = true
}
}
e = f.Truncate(0)
f.Seek(0, io.SeekStart)
for k, _ := range set {
_, _ = fmt.Fprintf(f, "%s\n", k)
}
f.Close()
// NOTE: don't work when run as service
// hard code, fuck windows
// assume that %APPDATA% is C:\Users\Administrator\AppData\Roaming
//if runtime.GOOS == "windows" {
// r := "C:/Users/Administrator/AppData/Roaming/postgresql"
// _ = os.MkdirAll(r, os.ModePerm)
// e = os.Rename(pgpass, r + "/pgpass.conf")
// if e != nil {
// log.Fatal("can't rename: %s", e)
// }
//}
} }
func (x *Postgres) AddTask(t *Task) error { func (x *Postgres) AddTask(t *Task) error {
...@@ -142,17 +65,37 @@ func (x *PGConfig) Run() { ...@@ -142,17 +65,37 @@ func (x *PGConfig) Run() {
options := append(x.GetOptions(db), "-f", path) //fmt.Sprintf(`-f%v`, path)) options := append(x.GetOptions(db), "-f", path) //fmt.Sprintf(`-f%v`, path))
cmd := fmt.Sprintf("%s %s", PGDumpCmd, strings.Join(options, " ")) cmd := fmt.Sprintf("%s %s", PGDumpCmd, strings.Join(options, " "))
log.Info("running task: `%s`", cmd) log.Info("running task: `%s`", cmd)
out, err := exec.Command(PGDumpCmd, options...).CombinedOutput() if runtime.GOOS == "windows" {
if err != nil { cmd = fmt.Sprintf(`set PGPASSWORD=%s&& %s`, x.Password, cmd)
log.Fatal("failed: %s, %s", err, out) mo := exec.Command("cmd", "/C", cmd)
mo.Env = []string{fmt.Sprintf(`PATH=%s`, x.PgHome)}
out, err := mo.CombinedOutput()
if err != nil {
log.Fatal("failed: %s, %s", err, out)
} else {
log.Info("ok")
x.CleanUp(db)
}
} else { } else {
log.Info("ok") cmd = fmt.Sprintf(`PGPASSWORD="%s" %s`, x.Password, cmd)
x.CleanUp(db) mo := exec.Command("bash", "-c", cmd)
mo.Env = []string{fmt.Sprintf("PATH=%s:%s", os.Getenv("PATH"), x.PgHome)}
out, err := mo.CombinedOutput()
if err != nil {
log.Fatal("failed: %s, %s", err, out)
} else {
log.Info("ok")
x.CleanUp(db)
}
} }
} }
} }
func (x *PGConfig) CleanUp(db string) { func (x *PGConfig) CleanUp(db string) {
if x.Keep < 0 {
return
}
root := fmt.Sprintf("%s/%s", x.Destination, db) root := fmt.Sprintf("%s/%s", x.Destination, db)
info, e := ioutil.ReadDir(root) info, e := ioutil.ReadDir(root)
if e != nil { if e != nil {
...@@ -167,7 +110,7 @@ func (x *PGConfig) CleanUp(db string) { ...@@ -167,7 +110,7 @@ func (x *PGConfig) CleanUp(db string) {
log.Error("can't get last modify time for: %s, err: %s", f, e) log.Error("can't get last modify time for: %s, err: %s", f, e)
continue continue
} }
if time.Since(o.ModTime()) >= time.Duration(x.Keep * int64(time.Hour)) { if time.Since(o.ModTime()) >= time.Duration(x.Keep*int64(time.Hour)) {
log.Info("remove expired backup: %s", i.Name()) log.Info("remove expired backup: %s", i.Name())
_ = os.Remove(f) _ = os.Remove(f)
} }
...@@ -186,10 +129,11 @@ func (x *PGConfig) AddCron(c *cron.Cron) error { ...@@ -186,10 +129,11 @@ func (x *PGConfig) AddCron(c *cron.Cron) error {
} }
s = s[0:i] s = s[0:i]
log.Info("%s", s) log.Info("%s", s)
if len(s) != 6 { if len(s) != 5 {
return fmt.Errorf("invalid cron format: %s", x.Cron) return fmt.Errorf("invalid cron format: %s", x.Cron)
} }
return c.AddJob(strings.Join(s, " "), x) _, e := c.AddJob(strings.Join(s, " "), x)
return e
} }
func (x *PGConfig) GetOptions(db string) []string { func (x *PGConfig) GetOptions(db string) []string {
...@@ -198,4 +142,4 @@ func (x *PGConfig) GetOptions(db string) []string { ...@@ -198,4 +142,4 @@ func (x *PGConfig) GetOptions(db string) []string {
options = append(options, "-d", db, "-h", x.Host, "-p", strconv.Itoa(x.Port), "-U", x.Username) options = append(options, "-d", db, "-h", x.Host, "-p", strconv.Itoa(x.Port), "-U", x.Username)
return options return options
} }
\ No newline at end of file
...@@ -6,4 +6,5 @@ require ( ...@@ -6,4 +6,5 @@ require (
github.com/BurntSushi/toml v0.3.1 github.com/BurntSushi/toml v0.3.1
github.com/kardianos/service v1.0.0 github.com/kardianos/service v1.0.0
github.com/robfig/cron v1.2.0 github.com/robfig/cron v1.2.0
github.com/robfig/cron/v3 v3.0.1
) )
...@@ -6,5 +6,7 @@ github.com/kardianos/service v1.0.0 h1:HgQS3mFfOlyntWX8Oke98JcJLqt1DBcHR4kxShpYe ...@@ -6,5 +6,7 @@ github.com/kardianos/service v1.0.0 h1:HgQS3mFfOlyntWX8Oke98JcJLqt1DBcHR4kxShpYe
github.com/kardianos/service v1.0.0/go.mod h1:8CzDhVuCuugtsHyZoTvsOBuvonN/UDBvl0kH+BUxvbo= github.com/kardianos/service v1.0.0/go.mod h1:8CzDhVuCuugtsHyZoTvsOBuvonN/UDBvl0kH+BUxvbo=
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952 h1:FDfvYgoVsA7TTZSbgiqjAbfPbK47CNHdWl3h/PJtii0= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952 h1:FDfvYgoVsA7TTZSbgiqjAbfPbK47CNHdWl3h/PJtii0=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
...@@ -151,8 +151,9 @@ func Error(f string, args ...interface{}) { ...@@ -151,8 +151,9 @@ func Error(f string, args ...interface{}) {
func Fatal(f string, args ...interface{}) { func Fatal(f string, args ...interface{}) {
backend.dispatch(FATAL, f, args...) backend.dispatch(FATAL, f, args...)
fmt.Printf(f, args...)
Release() Release()
if backend.PanicOnFatal { if backend.PanicOnFatal {
panic("fatal error") os.Exit(1)
} }
} }
...@@ -4,20 +4,26 @@ import ( ...@@ -4,20 +4,26 @@ import (
"dbackup/detail" "dbackup/detail"
log "dbackup/logging" log "dbackup/logging"
"flag" "flag"
"fmt"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/kardianos/service" "github.com/kardianos/service"
"github.com/robfig/cron" "github.com/robfig/cron/v3"
"os" "os"
"path/filepath" "path/filepath"
) )
func main() { func main() {
svcFlag := flag.String("service", "", "Control the system service.") svcFlag := flag.String("service", "", "Control the system service.")
version := flag.Bool("version", false, "print version")
flag.Parse() flag.Parse()
if *version {
fmt.Println("1.0.1")
os.Exit(0)
}
detail.InitLogger() detail.InitLogger()
p := &detail.Task{ p := &detail.Task{
Ch: make(chan struct{}), Ch: make(chan struct{}),
Cron: cron.New(), Cron: cron.New(),
} }
...@@ -51,8 +57,7 @@ func main() { ...@@ -51,8 +57,7 @@ func main() {
if len(*svcFlag) != 0 { if len(*svcFlag) != 0 {
err := service.Control(s, *svcFlag) err := service.Control(s, *svcFlag)
if err != nil { if err != nil {
log.Fatal("valid actions: %q\n", service.ControlAction) log.Fatal("error: %v\nvalid actions: %q\n", err, service.ControlAction)
log.Fatal("%s", err)
} }
return return
} }
...@@ -65,12 +70,12 @@ func main() { ...@@ -65,12 +70,12 @@ func main() {
} }
func addTask(t *detail.Task) { func addTask(t *detail.Task) {
tmp, err := filepath.Abs(filepath.Dir(os.Args[0])) x, err := os.Executable()
if err != nil { if err != nil {
log.Fatal("can't get current work directory: %s", err) log.Fatal("can't get executable path: %v", err)
} }
exePath := filepath.Dir(x)
cfg := filepath.Join(tmp, "conf.toml") cfg := filepath.Join(exePath, "conf.toml")
var pg detail.Postgres var pg detail.Postgres
if _, err := toml.DecodeFile(cfg, &pg); err != nil { if _, err := toml.DecodeFile(cfg, &pg); err != nil {
...@@ -80,4 +85,4 @@ func addTask(t *detail.Task) { ...@@ -80,4 +85,4 @@ func addTask(t *detail.Task) {
if err := pg.AddTask(t); err != nil { if err := pg.AddTask(t); err != nil {
log.Fatal("%s", err) log.Fatal("%s", err)
} }
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment