-
Notifications
You must be signed in to change notification settings - Fork 1
/
backup.go
178 lines (142 loc) · 4.31 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
package main
import (
"flag"
"fmt"
"github.com/mmckeen/btrfs-backup/btrfs"
"github.com/mmckeen/btrfs-backup/config"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"net/rpc"
"os"
"runtime"
)
func main() {
// so that defers work as intended
os.Exit(realMain())
}
// realMain is executed from main and returns the exit status to exit with.
func realMain() int {
// If there is no explicit number of Go threads to use, then set it
if os.Getenv("GOMAXPROCS") == "" {
runtime.GOMAXPROCS(runtime.NumCPU())
}
// Reset the log variables to minimize work in the subprocess
os.Setenv("BTRFS_BACKUP_LOG", "")
os.Setenv("BTRFS_BACKUP_LOG_FILE", "")
err := process()
if err != nil {
log.SetOutput(os.Stderr)
log.Printf("%s", err)
return 1
}
return 0
}
// Do the majority of the application work,
// spawn off backup jobs, the like
//
// returns exit status for program
func process() error {
// parse command line args
subvolume_source := flag.String("subvolume", config.DefaultConfig().SubvolumePath, "Subvolume to back up.")
subvolume_destination_directory := flag.String("destination_subvolume", config.DefaultConfig().SubvolumeDirectoryPath,
"A relative path off of the subvolume path that will come to store snapshots.")
server := flag.Bool("server", config.DefaultConfig().Server, "Whether to enable listening as a backup server.")
destination_host := flag.String("host", config.DefaultConfig().DestinationHost, "Host to send backups to.")
destination_port := flag.Int("port", config.DefaultConfig().DestinationPort,
"TCP port of host to send backups to. "+
"Will also be the port to listen on in server mode.")
flag.Parse()
// header info
info()
// set backup configuration
backupConfig := config.Config{*subvolume_source, *subvolume_destination_directory, *server, *destination_host, *destination_port}
// create drivers
btrfs_driver := new(btrfs.Btrfs)
btrfs_driver.BackupConfig = backupConfig
// validate
err := validateConfig(backupConfig, btrfs_driver)
if err != nil {
return err
}
// start server if asked
RPC := new(btrfs.BtrfsRPC)
RPC.Driver = btrfs_driver
if backupConfig.Server {
rpc.Register(RPC)
rpc.HandleHTTP()
l, e := net.Listen("tcp", ":1234")
if e != nil {
log.Fatal("listen error:", e)
}
http.Serve(l, nil)
} else {
// otherwise we are a client. Query the client for a list of snapshots to send!
client, err := rpc.DialHTTP("tcp", backupConfig.DestinationHost+":"+string(backupConfig.DestinationPort))
if err != nil {
log.Fatal("dialing:", err)
}
// Synchronous call
subvols, err := btrfs_driver.Subvolumes(backupConfig)
args := btrfs.Args{subvols}
var reply []string
err = client.Call("BtrfsRPC.SnapshotsNeeded", args, &reply)
if err != nil {
log.Fatal("arith error:", err)
}
for i := 0; i < len(subvols); i++ {
// Send all missing snapshots to other server
// tell the other side to start receiving first
btrfs_driver.SendSubvolume(subvols[i])
}
}
return nil
}
// validate the config object
func validateConfig(backupConfig config.Config, driver *btrfs.Btrfs) error {
// check to see if subvolume exists
// do other sanity checks
err := driver.Prepare(backupConfig)
if err != nil {
return err
}
// make sure that port number makes sense
err = fmt.Errorf("Invalid port number: %d", backupConfig.DestinationPort)
if backupConfig.DestinationPort > 65535 || backupConfig.DestinationPort < 1024 {
return err
}
// do initial testing of system by listing subvolumes
// and perform an initial snapshot for purposes of use later
subvols, err := driver.Subvolumes(backupConfig)
if err != nil && subvols == nil {
return err
}
_, err2 := driver.Snapshot(backupConfig, "/")
if err2 != nil {
return err2
}
return nil
}
// Print some basic application info
func info() {
log.SetOutput(os.Stderr)
log.Printf("Btrfs Backup Target OS/Arch: %s %s", runtime.GOOS, runtime.GOARCH)
log.Printf("Built with Go Version: %s", runtime.Version())
}
// logOutput determines where we should send logs (if anywhere).
func logOutput() (logOutput io.Writer, err error) {
logOutput = ioutil.Discard
if os.Getenv("BTRFS_BACKUP_LOG") != "" {
logOutput = os.Stderr
if logPath := os.Getenv("BTRFS_BACKUP_LOG_PATH"); logPath != "" {
var err error
logOutput, err = os.Create(logPath)
if err != nil {
return nil, err
}
}
}
return
}