-
Notifications
You must be signed in to change notification settings - Fork 0
/
backup.go
236 lines (221 loc) · 6.72 KB
/
backup.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
package backive
import (
"encoding/json"
"fmt"
"io"
"log"
"os"
"os/exec"
"path"
"strings"
"time"
)
// Mockings
var mockCmdRun = func(c *exec.Cmd) error {
return c.Run()
}
// Backup contains all necessary information for executing a configured backup.
type Backup struct {
Name string `mapstructure:",omitempty"`
TargetDevice string `mapstructure:"targetDevice"`
TargetPath string `mapstructure:"targetPath"`
SourcePath string `mapstructure:"sourcePath"`
ScriptPath interface{} `mapstructure:"scriptPath"`
Frequency int `mapstructure:"frequency"`
ExeUser string `mapstructure:"user,omitempty"`
Label string `mapstructure:"label,omitempty"`
logger *log.Logger
}
// Backups is nothing else than a name to Backup type mapping
type Backups map[string]*Backup
// FindBackupsForDevice only finds the first backup which is configured for a given device.
func (bs *Backups) FindBackupsForDevice(d Device) ([]*Backup, bool) {
var backups = []*Backup{}
for _, b := range *bs {
if d.Name == b.TargetDevice {
backups = append(backups, b)
}
}
var ret = len(backups) > 0
return backups, ret
}
// CanRun Checks the configuration items required and checks the frequency setting with the run database if a Backup should run.
func (b *Backup) CanRun() error {
// target path MUST exist
if b.TargetPath == "" {
return fmt.Errorf("the setting targetPath MUST exist within a backup configuration")
}
// script must exist, having only script means this is handled in the script
if b.ScriptPath == nil {
return fmt.Errorf("the setting scriptPath must exist within a backup configuration")
}
if !b.ShouldRun() {
return fmt.Errorf("frequency (days inbetween) not reached")
}
return nil
}
// PrepareRun prepares a run for a backup, creates a logger for the execution of the backup script and gives the rights of the directory recursively to the user specified.
func (b *Backup) PrepareRun() error {
backupPath := path.Join(
config.Settings.SystemMountPoint,
b.TargetDevice,
b.TargetPath,
)
CreateDirectoryIfNotExists(backupPath)
// configure extra logger
logdir := config.Settings.LogLocation
CreateDirectoryIfNotExists(logdir)
logname := path.Join(logdir, b.Name) + ".log"
logfile, err := os.OpenFile(logname, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
log.Println("Error creating logfile!")
return err
}
writer := io.MultiWriter(logfile)
b.logger = log.New(writer, b.Name, log.LstdFlags)
cmd := exec.Command("chown", "-R", b.ExeUser, backupPath)
err = mockCmdRun(cmd)
if err != nil {
b.logger.Printf("chown for backup directory failed: %s", err)
return err
}
b.logger.Printf("Backup %s prepared.", b.Name)
return nil
}
// Run runs the backup script with appropriate rights.
func (b *Backup) Run() error {
log.Printf("Running backup '%s'.", b.Name)
dev, ok := config.Devices[b.TargetDevice]
if ok {
log.Printf("Device found: %s (%s).", dev.Name, dev.UUID)
} else {
log.Printf("Device %s not found", b.TargetDevice)
return fmt.Errorf("device %s not found", b.TargetDevice)
}
if ok && dev.IsMounted() {
var scriptWArgs []string
switch slice := b.ScriptPath.(type) {
case []interface{}:
for _, v := range slice {
scriptWArgs = append(scriptWArgs, v.(string))
}
case []string:
for _, v := range slice {
scriptWArgs = append(scriptWArgs, v)
}
case string:
scriptWArgs = append(scriptWArgs, slice)
default:
log.Print("Fuck, the var is nothing we predicted...")
}
if !strings.ContainsAny(scriptWArgs[0], "/") || strings.HasPrefix(scriptWArgs[0], ".") {
//The scriptPath is a relative path, from the place of the config, so use the config as base
log.Printf("ERROR: Script path is relative, aborting.")
return fmt.Errorf("script path is relative, aborting")
}
var cmd *exec.Cmd
var args []string
if b.ExeUser != "" {
// setup script environment including user to use
args = []string{"-E", "-u", b.ExeUser, "/usr/bin/sh"}
args = append(args, scriptWArgs...)
cmd = exec.Command("sudo", args...)
} else {
args = []string{}
args = append(args, scriptWArgs...)
cmd = exec.Command("/usr/bin/sh", args...)
}
b.logger.Printf("Running backup script of '%s'", b.Name)
b.logger.Printf("Script is: %s", b.ScriptPath)
b.logger.Printf("Full command is: %s", cmd.String())
cmd.Stdout = b.logger.Writer()
cmd.Stderr = b.logger.Writer()
cmd.Env = []string{
fmt.Sprintf("BACKIVE_MOUNT=%s", config.Settings.SystemMountPoint),
fmt.Sprintf("BACKIVE_TO=%s",
path.Join(config.Settings.SystemMountPoint, dev.Name, b.TargetPath),
),
fmt.Sprintf("BACKIVE_FROM=%s", b.SourcePath),
}
log.Printf("Environment for process: %s", cmd.Env)
cmd.Dir = path.Join(config.Settings.SystemMountPoint, dev.Name)
log.Printf("About to run: %s", cmd.String())
// run script
err := mockCmdRun(cmd)
if err != nil {
log.Printf("Backup '%s' run failed", b.Name)
return err
}
runs.RegisterRun(b)
return nil
}
// quit with error that the device is not available.
return fmt.Errorf("the device is not mounted")
}
// Runs contains the Data for the scheduler: mapping from backups to a list of timestamps of the last 10 backups
type Runs struct {
data map[string][]time.Time
}
// Load loads the data from the json database
func (r *Runs) Load(db Database) {
data := db.data["runs"]
if data != "" {
runerr := json.Unmarshal([]byte(db.data["runs"]), &r.data)
if runerr != nil {
panic(runerr)
}
}
}
// Save saves the data into the json database
func (r *Runs) Save(db Database) {
if db.data == nil {
db.data = map[string]string{}
}
str, err := json.Marshal(r.data)
if err != nil {
panic(err)
}
db.data["runs"] = string(str)
}
// ShouldRun Takes a backup key and returns a bool if a backup should run now.
func (b *Backup) ShouldRun() bool {
freq := b.Frequency
// calculate time difference from last run, return true if no run has taken place
lr, ok := runs.LastRun(b)
if ok == nil {
dur := time.Since(lr)
days := dur.Hours() / 24
if days >= float64(freq) {
return true
}
}
if freq == 0 {
return true
}
return false
}
// RegisterRun saves a date of a backup run into the internal storage
func (r *Runs) RegisterRun(b *Backup) {
if r.data == nil {
r.data = map[string][]time.Time{}
}
nbl, ok := r.data[b.Name]
if !ok {
nbl = make([]time.Time, 1)
}
nbl = append([]time.Time{time.Now()}, nbl...)
r.data[b.Name] = nbl
r.Save(database)
}
// LastRun returns the time.Time of the last run of the backup given.
func (r *Runs) LastRun(b *Backup) (time.Time, error) {
_, ok := r.data[b.Name]
if ok {
slice := r.data[b.Name]
if len(slice) > 0 {
var t = time.Time(slice[0])
return t, nil
}
}
return time.Unix(0, 0), fmt.Errorf("backup name not found and therefore has never run")
}