Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in / Register
Toggle navigation
D
dbackup
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Packages
Packages
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Leon Li
dbackup
Commits
62bf8885
Commit
62bf8885
authored
Aug 14, 2019
by
abbycin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
done
parent
27ed8384
Changes
9
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
267 additions
and
82 deletions
+267
-82
.gitignore
.gitignore
+2
-1
README.md
README.md
+57
-1
conf.toml
conf.toml
+17
-18
common.go
detail/common.go
+23
-4
postgresql.go
detail/postgresql.go
+146
-33
example.png
example.png
+0
-0
go.mod
go.mod
+1
-1
logging.go
logging/logging.go
+1
-0
main.go
main.go
+20
-24
No files found.
.gitignore
View file @
62bf8885
.idea
.idea
*.exe
*.exe
*~
*~
log
\ No newline at end of file
README.md
View file @
62bf8885
## dbackup
## dbackup
database backup service
备份数据库服务
## 配置
配置使用TOML格式,示例如下:
```
toml
[[PGConfig]]
host
=
"127.0.0.1"
port
=
5432
db
=
[
"test"
,
"postgres"
]
username
=
"abby"
password
=
"abby"
destination
=
"D:/backup"
keep
=
3
cron
=
"0 */1 * * * *"
# 每分钟的第0秒运行
options
=
[
"-Fc"
,
"--no-owner"
]
[[PGConfig]]
host
=
"127.0.0.1"
port
=
5433
db
=
[
"db1"
,
"db2"
]
username
=
"admin"
password
=
"admin"
destination
=
"/data/backup"
keep
=
10
cron
=
"0 0 1 * * *"
# 每天1点0分0秒运行
options
=
["--inserts"]
```
配置中
`keep`
表示保留最近 N 天,
`cron`
表示何时执行备份任务,6个
`*`
分别表示: 秒, 分, 时, 本月第几天,月,本月第几天,举例如下:
-
`33 1 * * * *`
每个小时的 1 分 33 秒执行
-
`*/1 * * * * *`
每秒执行
-
`0 */1 * * * * `
每分钟执行
-
`23 30 1 * * *`
每天 1 时 30 分 23 秒执行
更多参考
[
这里
](
https://godoc.org/github.com/robfig/cron
)
## 用法
1, Windows/Linux下直接运行
2, Linux下可选择安装为服务
`./dbackup -service install`
(以root用户运行)
可用参数有
`-service start`
,
`-service stop`
,
`-service restart`
,
`-service install`
,
`-service uninstall`
程序会读取程序所在目录下的
`conf.toml`
配置,并在当前目录下创建
`log`
目录用于记录日志
## 注意
1,必须保证
`pg_dump`
在环境变量中
2, 程序只会使用必要的参数用于连接数据库,额外的参数可以通过
`options`
传递
3, 输出备份文件名为
`db_yyyymmdd-HHMMSS.dump`
且不可配置,存放位置为
`destination/db/db_yyyymmdd-HHMMSS.dump`
3, windows中程序不能作为服务运行(因为pg_dump找不到pgpass)
## 示例
每分钟备份一次,保留最近3分钟的备份
[
example
](
./example.png
)
\ No newline at end of file
conf.toml
View file @
62bf8885
[[
postgresql
]]
[[
PGConfig
]]
host
=
"127.0.0.1"
host
=
"127.0.0.1"
port
=
5432
port
=
5432
db
=
[
"
db1"
,
"db2
"
]
db
=
[
"
test"
,
"postgres
"
]
username
=
"
postgres
"
username
=
"
abby
"
password
=
"
postgres
"
password
=
"
abby
"
destination
=
"
/data/archive
"
destination
=
"
D:/backup
"
keep
=
10
keep
=
10
# M H d m w
cron
=
"0 30 1 * * *"
# run every day at 01:30:00
cron
=
"10 2 * * *"
options
=
[
"-Fc"
,
"--no-owner"
]
options
=
[
"-Fc"
,
"--no-onwer"
]
[[postgresql]]
#[[PGConfig]]
host
=
"127.0.0.1"
#host = "127.0.0.1"
port
=
5433
#port = 5433
db
=
[
"db1"
,
"db2"
]
#db = ["db1", "db2"]
username
=
"admin"
#username = "admin"
password
=
"admin"
#password = "admin"
destination
=
"/data/backup"
#destination = "/data/backup"
keep
=
10
#keep = 10
cron
=
"0 1 * * *"
#cron = "0 0 1 * * *" # run every day at 01:00:00
options
=
["--inserts"]
#options = ["--inserts"]
\ No newline at end of file
\ No newline at end of file
detail/common.go
View file @
62bf8885
...
@@ -5,16 +5,32 @@ import (
...
@@ -5,16 +5,32 @@ import (
"github.com/kardianos/service"
"github.com/kardianos/service"
"github.com/robfig/cron"
"github.com/robfig/cron"
"os"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"time"
)
)
func
InitLogger
()
{
func
InitLogger
()
{
_
=
log
.
Init
(
&
log
.
Logger
{
curr
,
e
:=
filepath
.
Abs
(
filepath
.
Dir
(
os
.
Args
[
0
]))
FileName
:
os
.
TempDir
()
+
"/dbackup.log"
,
if
e
!=
nil
{
curr
=
filepath
.
Join
(
os
.
TempDir
(),
"dbackup.log"
)
}
else
{
curr
=
filepath
.
Join
(
curr
,
"log"
)
_
=
os
.
MkdirAll
(
curr
,
os
.
ModePerm
)
curr
=
filepath
.
Join
(
curr
,
"dbackup.log"
)
}
if
runtime
.
GOOS
==
"windows"
{
curr
=
strings
.
ReplaceAll
(
curr
,
"
\\
"
,
"/"
)
}
e
=
log
.
Init
(
&
log
.
Logger
{
FileName
:
curr
,
RollSize
:
1
<<
30
,
RollSize
:
1
<<
30
,
RollInterval
:
time
.
Hour
*
30
,
RollInterval
:
time
.
Hour
*
30
,
Level
:
log
.
DEBUG
,
Level
:
log
.
DEBUG
,
PanicOnFatal
:
fals
e
,
PanicOnFatal
:
tru
e
,
})
})
}
}
...
@@ -29,11 +45,13 @@ type Task struct {
...
@@ -29,11 +45,13 @@ type Task struct {
}
}
func
(
t
*
Task
)
Start
(
srv
service
.
Service
)
error
{
func
(
t
*
Task
)
Start
(
srv
service
.
Service
)
error
{
log
.
Info
(
"starting..."
)
go
t
.
Run
()
return
nil
return
nil
}
}
func
(
t
*
Task
)
Run
()
{
func
(
t
*
Task
)
Run
()
{
log
.
Info
(
"running..."
)
t
.
Cron
.
Start
()
t
.
Cron
.
Start
()
<-
t
.
Ch
<-
t
.
Ch
log
.
Info
(
"stopped"
)
log
.
Info
(
"stopped"
)
...
@@ -43,6 +61,7 @@ func (t* Task) Run() {
...
@@ -43,6 +61,7 @@ func (t* Task) Run() {
func
(
t
*
Task
)
Stop
(
srv
service
.
Service
)
error
{
func
(
t
*
Task
)
Stop
(
srv
service
.
Service
)
error
{
log
.
Info
(
"stopping..."
)
log
.
Info
(
"stopping..."
)
t
.
Cron
.
Stop
()
t
.
Ch
<-
struct
{}{}
t
.
Ch
<-
struct
{}{}
return
nil
return
nil
}
}
...
...
detail/postgresql.go
View file @
62bf8885
package
detail
package
detail
import
(
import
(
"bufio"
log
"dbackup/logging"
log
"dbackup/logging"
"fmt"
"fmt"
"github.com/robfig/cron"
"github.com/robfig/cron"
"io"
"io/ioutil"
"os"
"os/exec"
"os/exec"
user2
"os/user"
"path/filepath"
"runtime"
"strconv"
"strings"
"strings"
"time"
"time"
)
)
...
@@ -14,22 +22,111 @@ const (
...
@@ -14,22 +22,111 @@ const (
)
)
type
PGConfig
struct
{
type
PGConfig
struct
{
Host
string
Host
string
`toml:"host"`
Port
string
Port
int
`toml:"port"`
DB
string
DB
[]
string
`toml:"db"`
Username
string
Username
string
`toml:"username"`
Password
string
Password
string
`toml:"password"`
Destination
string
Destination
string
`toml:"destination"`
Keep
int
Keep
int64
`toml:"keep"`
Cron
string
Cron
string
`toml:"cron"`
Options
[]
string
Options
[]
string
`toml:"options"`
}
}
type
Postgres
struct
{
type
Postgres
struct
{
PGConfig
[]
PGConfig
PGConfig
[]
PGConfig
}
}
func
(
x
*
Postgres
)
Setup
()
{
for
_
,
c
:=
range
x
.
PGConfig
{
if
strings
.
Index
(
c
.
Destination
,
"
\\
"
)
!=
-
1
{
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
{
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
{
x
.
Setup
()
log
.
Info
(
"postgres adding task, count: %d"
,
len
(
x
.
PGConfig
))
for
_
,
cfg
:=
range
x
.
PGConfig
{
for
_
,
cfg
:=
range
x
.
PGConfig
{
if
e
:=
cfg
.
AddCron
(
t
.
Cron
);
e
!=
nil
{
if
e
:=
cfg
.
AddCron
(
t
.
Cron
);
e
!=
nil
{
return
e
return
e
...
@@ -39,13 +136,42 @@ func (x *Postgres) AddTask(t *Task) error {
...
@@ -39,13 +136,42 @@ func (x *Postgres) AddTask(t *Task) error {
}
}
func
(
x
*
PGConfig
)
Run
()
{
func
(
x
*
PGConfig
)
Run
()
{
path
:=
fmt
.
Sprintf
(
`%s/%v_%v.dump`
,
x
.
Destination
,
x
.
DB
,
time
.
Now
()
.
Unix
())
log
.
Info
(
"backup postgresql..."
)
options
:=
append
(
x
.
GetOptions
(),
"-Fc"
,
fmt
.
Sprintf
(
`-f%v`
,
path
))
for
_
,
db
:=
range
x
.
DB
{
out
,
err
:=
exec
.
Command
(
PGDumpCmd
,
options
...
)
.
Output
()
path
:=
fmt
.
Sprintf
(
`%s/%s/%v_%v.dump`
,
x
.
Destination
,
db
,
db
,
time
.
Now
()
.
Format
(
"20060102-150405"
))
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
{
if
err
!=
nil
{
log
.
Fatal
(
"%s"
,
out
)
log
.
Fatal
(
"failed: %s, %s"
,
err
,
out
)
}
else
{
}
else
{
log
.
Info
(
"%s %s, ok"
,
PGDumpCmd
,
strings
.
Join
(
options
,
" "
))
log
.
Info
(
"ok"
)
x
.
CleanUp
(
db
)
}
}
}
func
(
x
*
PGConfig
)
CleanUp
(
db
string
)
{
root
:=
fmt
.
Sprintf
(
"%s/%s"
,
x
.
Destination
,
db
)
info
,
e
:=
ioutil
.
ReadDir
(
root
)
if
e
!=
nil
{
log
.
Fatal
(
"can't list dir: %s, err: %s"
,
x
.
Destination
,
e
)
}
for
_
,
i
:=
range
info
{
if
strings
.
Index
(
i
.
Name
(),
db
)
==
0
{
f
:=
fmt
.
Sprintf
(
"%s/%s"
,
root
,
i
.
Name
())
o
,
e
:=
os
.
Stat
(
f
)
if
e
!=
nil
{
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
))
{
log
.
Info
(
"remove expired backup: %s"
,
i
.
Name
())
_
=
os
.
Remove
(
f
)
}
}
}
}
}
}
...
@@ -59,30 +185,17 @@ func (x *PGConfig) AddCron(c *cron.Cron) error {
...
@@ -59,30 +185,17 @@ func (x *PGConfig) AddCron(c *cron.Cron) error {
}
}
}
}
s
=
s
[
0
:
i
]
s
=
s
[
0
:
i
]
if
len
(
s
)
!=
5
{
log
.
Info
(
"%s"
,
s
)
if
len
(
s
)
!=
6
{
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
)
return
c
.
AddJob
(
strings
.
Join
(
s
,
" "
),
x
)
}
}
func
(
x
*
PGConfig
)
GetOptions
()
[]
string
{
func
(
x
*
PGConfig
)
GetOptions
(
db
string
)
[]
string
{
options
:=
x
.
Options
options
:=
x
.
Options
if
x
.
DB
!=
""
{
options
=
append
(
options
,
"-d"
,
db
,
"-h"
,
x
.
Host
,
"-p"
,
strconv
.
Itoa
(
x
.
Port
),
"-U"
,
x
.
Username
)
options
=
append
(
options
,
fmt
.
Sprintf
(
`-d%v`
,
x
.
DB
))
}
if
x
.
Host
!=
""
{
options
=
append
(
options
,
fmt
.
Sprintf
(
`-h%v`
,
x
.
Host
))
}
if
x
.
Port
!=
""
{
options
=
append
(
options
,
fmt
.
Sprintf
(
`-p%v`
,
x
.
Port
))
}
if
x
.
Username
!=
""
{
options
=
append
(
options
,
fmt
.
Sprintf
(
`-U%v`
,
x
.
Username
))
}
return
options
return
options
}
}
\ No newline at end of file
example.png
0 → 100644
View file @
62bf8885
94.9 KB
go.mod
View file @
62bf8885
...
@@ -3,7 +3,7 @@ module dbackup
...
@@ -3,7 +3,7 @@ module dbackup
go 1.12
go 1.12
require (
require (
github.com/BurntSushi/toml v0.3.1
// indirect
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
)
)
logging/logging.go
View file @
62bf8885
...
@@ -151,6 +151,7 @@ func Error(f string, args ...interface{}) {
...
@@ -151,6 +151,7 @@ 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
...
)
Release
()
if
backend
.
PanicOnFatal
{
if
backend
.
PanicOnFatal
{
panic
(
"fatal error"
)
panic
(
"fatal error"
)
}
}
...
...
main.go
View file @
62bf8885
// +build windows
package
main
package
main
import
(
import
(
"dbackup/detail"
"dbackup/detail"
log
"dbackup/logging"
"flag"
"flag"
"
fmt
"
"
github.com/BurntSushi/toml
"
"github.com/kardianos/service"
"github.com/kardianos/service"
"github.com/robfig/cron"
"github.com/robfig/cron"
"log"
"os"
"os"
"path"
"path/filepath"
"runtime"
)
)
func
main
()
{
func
main
()
{
svcFlag
:=
flag
.
String
(
"service"
,
""
,
"Control the system service."
)
svcFlag
:=
flag
.
String
(
"service"
,
""
,
"Control the system service."
)
flag
.
Parse
()
flag
.
Parse
()
detail
.
InitLogger
()
p
:=
&
detail
.
Task
{
p
:=
&
detail
.
Task
{
Ch
:
make
(
chan
struct
{}),
Ch
:
make
(
chan
struct
{}),
...
@@ -25,8 +23,6 @@ func main() {
...
@@ -25,8 +23,6 @@ func main() {
addTask
(
p
)
addTask
(
p
)
detail
.
InitLogger
()
s
,
e
:=
service
.
New
(
p
,
&
service
.
Config
{
s
,
e
:=
service
.
New
(
p
,
&
service
.
Config
{
Name
:
"test"
,
Name
:
"test"
,
DisplayName
:
"test service"
,
DisplayName
:
"test service"
,
...
@@ -34,20 +30,20 @@ func main() {
...
@@ -34,20 +30,20 @@ func main() {
})
})
if
e
!=
nil
{
if
e
!=
nil
{
log
.
Fatal
(
e
)
log
.
Fatal
(
"%s"
,
e
)
}
}
errs
:=
make
(
chan
error
,
5
)
errs
:=
make
(
chan
error
,
5
)
lgg
,
e
:=
s
.
Logger
(
errs
)
lgg
,
e
:=
s
.
Logger
(
errs
)
if
e
!=
nil
{
if
e
!=
nil
{
log
.
Fatal
(
e
)
log
.
Fatal
(
"%s"
,
e
)
}
}
go
func
()
{
go
func
()
{
for
{
for
{
err
:=
<-
errs
err
:=
<-
errs
if
err
!=
nil
{
if
err
!=
nil
{
log
.
Print
(
err
)
log
.
Fatal
(
"%s"
,
err
)
}
}
}
}
}()
}()
...
@@ -55,8 +51,8 @@ func main() {
...
@@ -55,8 +51,8 @@ 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
.
Printf
(
"Valid actions: %q
\n
"
,
service
.
ControlAction
)
log
.
Fatal
(
"Valid actions: %q
\n
"
,
service
.
ControlAction
)
log
.
Fatal
(
err
)
log
.
Fatal
(
"%s"
,
err
)
}
}
return
return
}
}
...
@@ -69,19 +65,19 @@ func main() {
...
@@ -69,19 +65,19 @@ func main() {
}
}
func
addTask
(
t
*
detail
.
Task
)
{
func
addTask
(
t
*
detail
.
Task
)
{
bindir
:=
[]
byte
(
path
.
Dir
(
os
.
Args
[
0
]))
tmp
,
err
:=
filepath
.
Abs
(
filepath
.
Dir
(
os
.
Args
[
0
]))
if
runtime
.
GOOS
==
"windows"
{
if
err
!=
nil
{
i
:=
0
log
.
Fatal
(
"can't get current work directory: %s"
,
err
)
for
j
:=
0
;
j
<
len
(
bindir
);
j
++
{
if
bindir
[
j
]
!=
'\\'
{
bindir
[
i
]
=
bindir
[
j
]
i
+=
1
}
}
bindir
=
bindir
[
0
:
i
]
}
}
cfg
:=
f
mt
.
Sprintf
(
"%s/conf.toml"
,
string
(
bindir
)
)
cfg
:=
f
ilepath
.
Join
(
tmp
,
"conf.toml"
)
var
pg
detail
.
Postgres
if
_
,
err
:=
toml
.
DecodeFile
(
cfg
,
&
pg
);
err
!=
nil
{
log
.
Fatal
(
"%s"
,
err
)
}
if
err
:=
pg
.
AddTask
(
t
);
err
!=
nil
{
log
.
Fatal
(
"%s"
,
err
)
}
}
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment