Newer
Older
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
// Learn more about F# at http://fsharp.org
//
// - backup file list
// /%appdata%/mbackup/mbackup-default.list
// /%appdata%/mbackup/user-default.list
// /%appdata%/mbackup/local.list (optional)
// - exclude pattern
// /%appdata%/mbackup/mbackup-default.exclude
// /%appdata%/mbackup/local.exclude (optional)
open System
open System.Diagnostics
open Argu
let ExitBadParam = 1
let ExitTimeout = 2
type CLIArguments =
| DryRun
| Cron
| Target of backupTarget: string
with
interface IArgParserTemplate with
member s.Usage =
match s with
| DryRun _ -> "only show what will be done, do not transfer any file"
| Cron _ -> "run in cron mode, do not ask user any questions"
| Target _ -> "rsync target, could be local dir or remote ssh dir"
type Logger() =
let mutable level = Logger.DEBUG
static member DEBUG = 10
static member INFO = 20
static member WARNING = 30
static member ERROR = 40
static member LevelToString level =
match level with
| 10 -> "DEBUG"
| 20 -> "INFO"
| 30 -> "WARNING"
| 40 -> "ERROR"
| _ -> failwith (sprintf "Unknown log level: %d" level)
member this.Level = level
member this.LogMaybe level fmt =
if this.Level <= level then
Printf.ksprintf (
fun s ->
let time = DateTime.Now
printfn "%02d:%02d:%02d %s %s" time.Hour time.Minute time.Second (Logger.LevelToString level) s)
fmt
else
Printf.ksprintf (fun _ -> printfn "") fmt
member this.SetLevel level =
this.Level = level
member this.Debug = this.LogMaybe Logger.DEBUG
member this.Info = this.LogMaybe Logger.INFO
member this.Warning = this.LogMaybe Logger.WARNING
member this.Error = this.LogMaybe Logger.ERROR
let GetEnv (varName: string) = Environment.GetEnvironmentVariable varName
let GetEnvDefault (varName: string) (defaultValue: string) =
let value = Environment.GetEnvironmentVariable varName
match value with
| null -> defaultValue
| "" -> defaultValue
| _ -> value
// Convert windows path to Mingw64 path
let ToMingwPath windowsPath =
// TODO implement me
windowsPath
let appDataRoamingDir = ToMingwPath (Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData))
let appDataLocalDir = ToMingwPath (Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData))
// TODO make sure dir ends with /
let userConfigDir = appDataRoamingDir + "/mbackup/"
let runtimeDir = appDataLocalDir
let isLocalTarget (target: string) = target.StartsWith "/"
[<EntryPoint>]
let main argv =
let errorHandler = ProcessExiter(colorizer = function ErrorCode.HelpText -> None | _ -> Some ConsoleColor.Red)
let parser = ArgumentParser.Create<CLIArguments>(programName = "mbackup.exe", errorHandler = errorHandler)
let results = parser.Parse argv
let cron = results.Contains Cron
let dryRun = results.Contains DryRun
let logger = Logger()
logger.Info "userConfigDir=%s" userConfigDir
logger.Info "runtimeDir=%s" runtimeDir
let rsyncCmd: string list = []
let rsyncCmd = if dryRun then List.append rsyncCmd ["--dry-run"] else rsyncCmd
let rsyncCmd = List.append rsyncCmd ("-h --stats -air --delete --delete-excluded --ignore-missing-args".Split [|' '|] |> Array.toList)
let mbackupFile = runtimeDir + "mbackup.list"
let rsyncCmd = List.append rsyncCmd [sprintf "--files-from=%s" mbackupFile]
let excludeFile = userConfigDir + "mbackup-default.exclude"
let rsyncCmd = List.append rsyncCmd [sprintf "--exclude-from=%s" excludeFile]
let localExcludeFile = userConfigDir + "local.list"
let rsyncCmd = if IO.File.Exists localExcludeFile then List.append rsyncCmd [sprintf "--exclude-from=%s" localExcludeFile] else rsyncCmd
let localLogFile = runtimeDir + "mbackup.log"
let rsyncCmd = List.append rsyncCmd [sprintf "--log-file=%s" localLogFile]
let backupTarget = results.GetResult (Target, defaultValue = Environment.GetEnvironmentVariable "TARGET")
match backupTarget with
| null ->
logger.Error "TARGET is not defined"
ExitBadParam
| _ ->
let backupTarget = ToMingwPath backupTarget
let rsyncCmd =
if not (isLocalTarget backupTarget)
then
let hostname: string = Net.Dns.GetHostName()
let nodeName: string = GetEnvDefault "NODE_NAME" hostname
let remoteLogFile = sprintf "/var/log/mbackup/%s.log" nodeName
List.append rsyncCmd [sprintf "--remote-option=--log-file=%s" remoteLogFile]
else
rsyncCmd
let rsyncCmd = List.append rsyncCmd ["/"]
let rsyncCmd = List.append rsyncCmd [backupTarget]
let rsyncArgs = rsyncCmd |> String.concat " "
// TODO print rsyncArgs in the same Info call when Info supports multiple params.
logger.Info "Note: if you run the following rsync command yourself, make sure the generated file list (%s) is up-to-date." mbackupFile
let rsyncExe = "D:\\downloads\\apps\\rsync-w64-3.1.3-2-standalone\\usr\\bin\\rsync.exe"
let echoExe = "C:\\Program Files\\Git\\usr\\bin\\echo.exe"
let proc = Process.Start(echoExe, rsyncArgs)
logger.Info "%s" (rsyncExe + rsyncArgs)
if proc.WaitForExit Int32.MaxValue then
proc.ExitCode
else
logger.Error "mbackup timed out while waiting for rsync to complete"
ExitTimeout