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
5 years ago
by
abbycin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
done
parent
27ed8384
Changes
9
Hide 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
*.exe
*~
\ No newline at end of file
*~
log
\ No newline at end of file
This diff is collapsed.
Click to expand it.
README.md
View file @
62bf8885
## 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
This diff is collapsed.
Click to expand it.
conf.toml
View file @
62bf8885
[[
postgresql
]]
[[
PGConfig
]]
host
=
"127.0.0.1"
port
=
5432
db
=
[
"
db1"
,
"db2
"
]
username
=
"
postgres
"
password
=
"
postgres
"
destination
=
"
/data/archive
"
db
=
[
"
test"
,
"postgres
"
]
username
=
"
abby
"
password
=
"
abby
"
destination
=
"
D:/backup
"
keep
=
10
# M H d m w
cron
=
"10 2 * * *"
options
=
[
"-Fc"
,
"--no-onwer"
]
cron
=
"0 30 1 * * *"
# run every day at 01:30:00
options
=
[
"-Fc"
,
"--no-owner"
]
[[postgresql]]
host
=
"127.0.0.1"
port
=
5433
db
=
[
"db1"
,
"db2"
]
username
=
"admin"
password
=
"admin"
destination
=
"/data/backup"
keep
=
10
cron
=
"0 1 * * *"
options
=
["--inserts"]
\ No newline at end of file
#[[PGConfig]]
#host = "127.0.0.1"
#port = 5433
#db = ["db1", "db2"]
#username = "admin"
#password = "admin"
#destination = "/data/backup"
#keep = 10
#cron = "0 0 1 * * *" # run every day at 01:00:00
#options = ["--inserts"]
\ No newline at end of file
This diff is collapsed.
Click to expand it.
detail/common.go
View file @
62bf8885
...
...
@@ -5,16 +5,32 @@ import (
"github.com/kardianos/service"
"github.com/robfig/cron"
"os"
"path/filepath"
"runtime"
"strings"
"time"
)
func
InitLogger
()
{
_
=
log
.
Init
(
&
log
.
Logger
{
FileName
:
os
.
TempDir
()
+
"/dbackup.log"
,
curr
,
e
:=
filepath
.
Abs
(
filepath
.
Dir
(
os
.
Args
[
0
]))
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
,
RollInterval
:
time
.
Hour
*
30
,
Level
:
log
.
DEBUG
,
PanicOnFatal
:
fals
e
,
PanicOnFatal
:
tru
e
,
})
}
...
...
@@ -29,11 +45,13 @@ type Task struct {
}
func
(
t
*
Task
)
Start
(
srv
service
.
Service
)
error
{
log
.
Info
(
"starting..."
)
go
t
.
Run
()
return
nil
}
func
(
t
*
Task
)
Run
()
{
log
.
Info
(
"running..."
)
t
.
Cron
.
Start
()
<-
t
.
Ch
log
.
Info
(
"stopped"
)
...
...
@@ -43,6 +61,7 @@ func (t* Task) Run() {
func
(
t
*
Task
)
Stop
(
srv
service
.
Service
)
error
{
log
.
Info
(
"stopping..."
)
t
.
Cron
.
Stop
()
t
.
Ch
<-
struct
{}{}
return
nil
}
...
...
This diff is collapsed.
Click to expand it.
detail/postgresql.go
View file @
62bf8885
package
detail
import
(
"bufio"
log
"dbackup/logging"
"fmt"
"github.com/robfig/cron"
"io"
"io/ioutil"
"os"
"os/exec"
user2
"os/user"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
)
...
...
@@ -14,22 +22,111 @@ const (
)
type
PGConfig
struct
{
Host
string
Port
string
DB
string
Username
string
Password
string
Destination
string
Keep
int
Cron
string
Options
[]
string
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"`
Cron
string
`toml:"cron"`
Options
[]
string
`toml:"options"`
}
type
Postgres
struct
{
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
{
x
.
Setup
()
log
.
Info
(
"postgres adding task, count: %d"
,
len
(
x
.
PGConfig
))
for
_
,
cfg
:=
range
x
.
PGConfig
{
if
e
:=
cfg
.
AddCron
(
t
.
Cron
);
e
!=
nil
{
return
e
...
...
@@ -39,13 +136,42 @@ func (x *Postgres) AddTask(t *Task) error {
}
func
(
x
*
PGConfig
)
Run
()
{
path
:=
fmt
.
Sprintf
(
`%s/%v_%v.dump`
,
x
.
Destination
,
x
.
DB
,
time
.
Now
()
.
Unix
())
options
:=
append
(
x
.
GetOptions
(),
"-Fc"
,
fmt
.
Sprintf
(
`-f%v`
,
path
))
out
,
err
:=
exec
.
Command
(
PGDumpCmd
,
options
...
)
.
Output
()
if
err
!=
nil
{
log
.
Fatal
(
"%s"
,
out
)
}
else
{
log
.
Info
(
"%s %s, ok"
,
PGDumpCmd
,
strings
.
Join
(
options
,
" "
))
log
.
Info
(
"backup postgresql..."
)
for
_
,
db
:=
range
x
.
DB
{
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
{
log
.
Fatal
(
"failed: %s, %s"
,
err
,
out
)
}
else
{
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 {
}
}
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
c
.
AddJob
(
strings
.
Join
(
s
,
" "
),
x
)
}
func
(
x
*
PGConfig
)
GetOptions
()
[]
string
{
func
(
x
*
PGConfig
)
GetOptions
(
db
string
)
[]
string
{
options
:=
x
.
Options
if
x
.
DB
!=
""
{
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
))
}
options
=
append
(
options
,
"-d"
,
db
,
"-h"
,
x
.
Host
,
"-p"
,
strconv
.
Itoa
(
x
.
Port
),
"-U"
,
x
.
Username
)
return
options
}
\ No newline at end of file
This diff is collapsed.
Click to expand it.
example.png
0 → 100644
View file @
62bf8885
94.9 KB
This diff is collapsed.
Click to expand it.
go.mod
View file @
62bf8885
...
...
@@ -3,7 +3,7 @@ module dbackup
go 1.12
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/robfig/cron v1.2.0
)
This diff is collapsed.
Click to expand it.
logging/logging.go
View file @
62bf8885
...
...
@@ -151,6 +151,7 @@ func Error(f string, args ...interface{}) {
func
Fatal
(
f
string
,
args
...
interface
{})
{
backend
.
dispatch
(
FATAL
,
f
,
args
...
)
Release
()
if
backend
.
PanicOnFatal
{
panic
(
"fatal error"
)
}
...
...
This diff is collapsed.
Click to expand it.
main.go
View file @
62bf8885
// +build windows
package
main
import
(
"dbackup/detail"
log
"dbackup/logging"
"flag"
"
fmt
"
"
github.com/BurntSushi/toml
"
"github.com/kardianos/service"
"github.com/robfig/cron"
"log"
"os"
"path"
"runtime"
"path/filepath"
)
func
main
()
{
svcFlag
:=
flag
.
String
(
"service"
,
""
,
"Control the system service."
)
flag
.
Parse
()
detail
.
InitLogger
()
p
:=
&
detail
.
Task
{
Ch
:
make
(
chan
struct
{}),
...
...
@@ -25,8 +23,6 @@ func main() {
addTask
(
p
)
detail
.
InitLogger
()
s
,
e
:=
service
.
New
(
p
,
&
service
.
Config
{
Name
:
"test"
,
DisplayName
:
"test service"
,
...
...
@@ -34,20 +30,20 @@ func main() {
})
if
e
!=
nil
{
log
.
Fatal
(
e
)
log
.
Fatal
(
"%s"
,
e
)
}
errs
:=
make
(
chan
error
,
5
)
lgg
,
e
:=
s
.
Logger
(
errs
)
if
e
!=
nil
{
log
.
Fatal
(
e
)
log
.
Fatal
(
"%s"
,
e
)
}
go
func
()
{
for
{
err
:=
<-
errs
if
err
!=
nil
{
log
.
Print
(
err
)
log
.
Fatal
(
"%s"
,
err
)
}
}
}()
...
...
@@ -55,8 +51,8 @@ func main() {
if
len
(
*
svcFlag
)
!=
0
{
err
:=
service
.
Control
(
s
,
*
svcFlag
)
if
err
!=
nil
{
log
.
Printf
(
"Valid actions: %q
\n
"
,
service
.
ControlAction
)
log
.
Fatal
(
err
)
log
.
Fatal
(
"Valid actions: %q
\n
"
,
service
.
ControlAction
)
log
.
Fatal
(
"%s"
,
err
)
}
return
}
...
...
@@ -69,19 +65,19 @@ func main() {
}
func
addTask
(
t
*
detail
.
Task
)
{
bindir
:=
[]
byte
(
path
.
Dir
(
os
.
Args
[
0
]))
if
runtime
.
GOOS
==
"windows"
{
i
:=
0
for
j
:=
0
;
j
<
len
(
bindir
);
j
++
{
if
bindir
[
j
]
!=
'\\'
{
bindir
[
i
]
=
bindir
[
j
]
i
+=
1
}
}
bindir
=
bindir
[
0
:
i
]
tmp
,
err
:=
filepath
.
Abs
(
filepath
.
Dir
(
os
.
Args
[
0
]))
if
err
!=
nil
{
log
.
Fatal
(
"can't get current work directory: %s"
,
err
)
}
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
This diff is collapsed.
Click to expand it.
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