From b2e6741ffa9fe36f63d78197704e5426c00b0052 Mon Sep 17 00:00:00 2001 From: abbycin <abbytsing@gmail.com> Date: Tue, 10 Mar 2020 14:33:53 +0800 Subject: [PATCH] windows can also run as serivce now --- README.md | 20 +++---- conf.toml | 7 +-- detail/common.go | 17 +++--- detail/postgresql.go | 124 ++++++++++++------------------------------- go.mod | 1 + go.sum | 2 + logging/logging.go | 3 +- main.go | 23 ++++---- 8 files changed, 75 insertions(+), 122 deletions(-) diff --git a/README.md b/README.md index fcad2de..a643da1 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ é…置使用TOMLæ ¼å¼ï¼Œç¤ºä¾‹å¦‚下: ```toml [[PGConfig]] +pg_home = "C:/Program Files/PostgreSQL/10/bin" # pg 安装目录 host = "127.0.0.1" port = 5432 db = ["test", "postgres"] @@ -24,32 +25,31 @@ username = "admin" password = "admin" destination = "/data/backup" keep = 10 -cron = "0 0 1 * * *" # æ¯å¤©1点0分0ç§’è¿è¡Œ +cron = "* 1 * * *" # æ¯å¤©1点0分0ç§’è¿è¡Œ options = ["--inserts"] ``` -é…ç½®ä¸`keep`表示ä¿ç•™æœ€è¿‘ N 天, `cron`表示何时执行备份任务,6个`*` 分别表示: ç§’, 分, æ—¶, æœ¬æœˆç¬¬å‡ å¤©ï¼Œæœˆï¼Œæœ¬æœˆç¬¬å‡ å¤©ï¼Œä¸¾ä¾‹å¦‚ä¸‹ï¼š +é…ç½®ä¸`keep`表示ä¿ç•™æœ€è¿‘ N å°æ—¶ï¼Œ `cron`表示何时执行备份任务,5个`*` 分别表示: 分, æ—¶, æœ¬æœˆç¬¬å‡ å¤©ï¼Œæœˆï¼Œæœ¬å‘¨ç¬¬å‡ å¤©ï¼Œä¸¾ä¾‹å¦‚ä¸‹ï¼š -- `33 1 * * * *` æ¯ä¸ªå°æ—¶çš„ 1 分 33 秒执行 -- `*/1 * * * * *` æ¯ç§’执行 -- `0 */1 * * * * ` æ¯åˆ†é’Ÿæ‰§è¡Œ -- `23 30 1 * * *` æ¯å¤© 1 æ—¶ 30 分 23 秒执行 +- `*/1 * * * *` æ¯1分钟执行 +- `0 */1 * * *` æ¯å°1时执行 +- `0 0 */1 * * ` æ¯æœˆ1å·æ‰§è¡Œ +- `23 2 1 * *` æ¯æœˆ1å·2æ—¶23分执行 更多å‚考 [这里](https://godoc.org/github.com/robfig/cron) ## 用法 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` 程åºä¼šè¯»å–ç¨‹åºæ‰€åœ¨ç›®å½•下的`conf.toml`é…置,并在当å‰ç›®å½•下创建`log`目录用于记录日志 ## æ³¨æ„ -1,必须ä¿è¯`pg_dump` 在环境å˜é‡ä¸ +1, 如果å¯ä»¥æ‰¾åˆ°`pg_dump`那们é…ç½®ä¸çš„`pg_home`å¯ä»¥è®¾ç½®ä¸ºç©ºï¼ˆå³pg_home = "") 2, 程åºåªä¼šä½¿ç”¨å¿…è¦çš„傿•°ç”¨äºŽè¿žæŽ¥æ•°æ®åº“,é¢å¤–çš„å‚æ•°å¯ä»¥é€šè¿‡`options`ä¼ é€’ -3, 输出备份文件å为 `db_yyyymmdd-HHMMSS.dump` 且ä¸å¯é…ç½®ï¼Œå˜æ”¾ä½ç½®ä¸º `destination/db/db_yyyymmdd-HHMMSS.dump` -3, windowsä¸ç¨‹åºä¸èƒ½ä½œä¸ºæœåŠ¡è¿è¡Œ(å› ä¸ºpg_dump找ä¸åˆ°pgpass) +3, 输出备份文件å为 `db_yyyymmdd-HHMMSS.dump` 且ä¸å¯é…ç½®ï¼Œå˜æ”¾ä½ç½®ä¸º `destination/db/db_yyyymmdd-HHMMSS.dump` ## 示例 diff --git a/conf.toml b/conf.toml index c973614..a8b656e 100644 --- a/conf.toml +++ b/conf.toml @@ -1,12 +1,13 @@ [[PGConfig]] +pg_home = "C:/Program Files/PostgreSQL/10/bin" # where postgresql installed host = "127.0.0.1" port = 5432 db = ["test", "postgres"] username = "abby" password = "abby" destination = "D:/backup" -keep = 10 -cron = "0 30 1 * * *" # run every day at 01:30:00 +keep = 10 # keep latest 10 hours dump, set -1 to disable cleanup +cron = "1 * * * *" # run every 1 minute options = ["-Fc", "--no-owner"] #[[PGConfig]] @@ -17,5 +18,5 @@ options = ["-Fc", "--no-owner"] #password = "admin" #destination = "/data/backup" #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"] \ No newline at end of file diff --git a/detail/common.go b/detail/common.go index cf2ce20..d5c4f02 100644 --- a/detail/common.go +++ b/detail/common.go @@ -3,7 +3,7 @@ package detail import ( "dbackup/logging" "github.com/kardianos/service" - "github.com/robfig/cron" + "github.com/robfig/cron/v3" "os" "path/filepath" "runtime" @@ -26,21 +26,20 @@ func InitLogger() { } e = log.Init(&log.Logger{ - FileName: curr, - RollSize: 1 << 30, + FileName: curr, + RollSize: 1 << 30, RollInterval: time.Hour * 30, - Level: log.DEBUG, + Level: log.DEBUG, PanicOnFatal: true, }) } - type Job interface { AddTask(*Task) error } type Task struct { - Ch chan struct{} + Ch chan struct{} Cron *cron.Cron } @@ -50,10 +49,10 @@ func (t *Task) Start(srv service.Service) error { return nil } -func (t* Task) Run() { +func (t *Task) Run() { log.Info("running...") t.Cron.Start() - <- t.Ch + <-t.Ch log.Info("stopped") t.Cron.Stop() @@ -68,4 +67,4 @@ func (t *Task) Stop(srv service.Service) error { func (t *Task) AddTask(task Job) error { return task.AddTask(t) -} \ No newline at end of file +} diff --git a/detail/postgresql.go b/detail/postgresql.go index 43f0272..dc6ed52 100644 --- a/detail/postgresql.go +++ b/detail/postgresql.go @@ -1,16 +1,12 @@ package detail import ( - "bufio" log "dbackup/logging" "fmt" - "github.com/robfig/cron" - "io" + "github.com/robfig/cron/v3" "io/ioutil" "os" "os/exec" - user2 "os/user" - "path/filepath" "runtime" "strconv" "strings" @@ -22,13 +18,14 @@ const ( ) type PGConfig struct { + PgHome string `toml:"pg_home"` Host string `toml:"host"` Port int `toml:"port"` DB []string `toml:"db"` Username string `toml:"username"` Password string `toml:"password"` Destination string `toml:"destination"` - Keep int64 `toml:"keep"` + Keep int64 `toml:"keep"` Cron string `toml:"cron"` Options []string `toml:"options"` } @@ -43,85 +40,11 @@ func (x *Postgres) Setup() { log.Fatal("invalid destination: %s, must use Unix Path Seperator `/`") } 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) } } } - - 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 { @@ -142,17 +65,37 @@ func (x *PGConfig) Run() { options := append(x.GetOptions(db), "-f", path) //fmt.Sprintf(`-f%v`, path)) cmd := fmt.Sprintf("%s %s", PGDumpCmd, strings.Join(options, " ")) log.Info("running task: `%s`", cmd) - out, err := exec.Command(PGDumpCmd, options...).CombinedOutput() - if err != nil { - log.Fatal("failed: %s, %s", err, out) + if runtime.GOOS == "windows" { + cmd = fmt.Sprintf(`set PGPASSWORD=%s&& %s`, x.Password, cmd) + 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 { - log.Info("ok") - x.CleanUp(db) + cmd = fmt.Sprintf(`PGPASSWORD="%s" %s`, x.Password, cmd) + 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) { + if x.Keep < 0 { + return + } root := fmt.Sprintf("%s/%s", x.Destination, db) info, e := ioutil.ReadDir(root) if e != nil { @@ -167,7 +110,7 @@ func (x *PGConfig) CleanUp(db string) { log.Error("can't get last modify time for: %s, err: %s", f, e) 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()) _ = os.Remove(f) } @@ -186,10 +129,11 @@ func (x *PGConfig) AddCron(c *cron.Cron) error { } s = s[0:i] log.Info("%s", s) - if len(s) != 6 { + if len(s) != 5 { 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 { @@ -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) return options -} \ No newline at end of file +} diff --git a/go.mod b/go.mod index 4ee0d5d..100d4ff 100644 --- a/go.mod +++ b/go.mod @@ -6,4 +6,5 @@ require ( github.com/BurntSushi/toml v0.3.1 github.com/kardianos/service v1.0.0 github.com/robfig/cron v1.2.0 + github.com/robfig/cron/v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 4f2d2ff..529b8b9 100644 --- a/go.sum +++ b/go.sum @@ -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/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= 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/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/logging/logging.go b/logging/logging.go index fa72f25..39ab204 100644 --- a/logging/logging.go +++ b/logging/logging.go @@ -151,8 +151,9 @@ func Error(f string, args ...interface{}) { func Fatal(f string, args ...interface{}) { backend.dispatch(FATAL, f, args...) + fmt.Printf(f, args...) Release() if backend.PanicOnFatal { - panic("fatal error") + os.Exit(1) } } diff --git a/main.go b/main.go index e210357..55353e4 100644 --- a/main.go +++ b/main.go @@ -4,20 +4,26 @@ import ( "dbackup/detail" log "dbackup/logging" "flag" + "fmt" "github.com/BurntSushi/toml" "github.com/kardianos/service" - "github.com/robfig/cron" + "github.com/robfig/cron/v3" "os" "path/filepath" ) func main() { svcFlag := flag.String("service", "", "Control the system service.") + version := flag.Bool("version", false, "print version") flag.Parse() + if *version { + fmt.Println("1.0.1") + os.Exit(0) + } detail.InitLogger() p := &detail.Task{ - Ch: make(chan struct{}), + Ch: make(chan struct{}), Cron: cron.New(), } @@ -51,8 +57,7 @@ func main() { if len(*svcFlag) != 0 { err := service.Control(s, *svcFlag) if err != nil { - log.Fatal("valid actions: %q\n", service.ControlAction) - log.Fatal("%s", err) + log.Fatal("error: %v\nvalid actions: %q\n", err, service.ControlAction) } return } @@ -65,12 +70,12 @@ func main() { } func addTask(t *detail.Task) { - tmp, err := filepath.Abs(filepath.Dir(os.Args[0])) + x, err := os.Executable() if err != nil { - log.Fatal("can't get current work directory: %s", err) + log.Fatal("can't get executable path: %v", err) } - - cfg := filepath.Join(tmp, "conf.toml") + exePath := filepath.Dir(x) + cfg := filepath.Join(exePath, "conf.toml") var pg detail.Postgres if _, err := toml.DecodeFile(cfg, &pg); err != nil { @@ -80,4 +85,4 @@ func addTask(t *detail.Task) { if err := pg.AddTask(t); err != nil { log.Fatal("%s", err) } -} \ No newline at end of file +} -- 2.21.0