Commit 0e529268 authored by Yuanle Song's avatar Yuanle Song

support multiple ways of specifying target

cli option > env var > config file
parent 98d8bb36
......@@ -38,7 +38,7 @@ with
member s.Usage =
match s with
| Dry_Run _ -> "only show what will be done, do not transfer any file"
| Target _ -> "rsync target, could be local dir or remote ssh dir"
| Target _ -> "rsync target, could be local dir in Windows or mingw format or remote ssh dir"
| Remote_User _ -> "remote linux user to own the backup files"
| Itemize_Changes _ -> "add -i option to rsync"
| Node_Name _ -> "local node's name, used in remote logging"
......@@ -66,7 +66,9 @@ let runtimeDir = appDataLocalDir + "mbackup/"
let mbackupConfigFile = userConfigDirWin + "mbackup.txt"
let isLocalTarget (target: string) = target.StartsWith "/"
// return true if target is a local dir. local dir can be unix style or windows style.
let isLocalTarget (target: string) =
target.StartsWith "/" || Regex.IsMatch(target, "^[c-z]:", RegexOptions.IgnoreCase)
// expand user file to mingw64 rsync supported path.
// abc -> /cygdrive/c/Users/<user>/abc
......@@ -113,29 +115,29 @@ let generateMbackupList (logger: Logger) =
let lines = readMbackupListFile mbackupLocalList |> Seq.map toMingwPath
(true, lines)
with
| :? System.IO.FileNotFoundException ->
(true, Seq.empty)
| ex ->
logger.Error "Read mbackupLocalList failed: %s" ex.Message
(false, Seq.empty)
| :? System.IO.FileNotFoundException ->
(true, Seq.empty)
| ex ->
logger.Error "Read mbackupLocalList failed: %s" ex.Message
(false, Seq.empty)
match localListLinesMaybe with
| (false, _) -> failwith "Read mbackup local.list file failed"
| (true, localListLines) ->
let userDefaultListLines = readMbackupListFile mbackupUserDefaultList |> Seq.map expandUserFile
let allLines = Seq.append (Seq.append defaultListLines localListLines) userDefaultListLines
// For mbackup-default.list and local.list, exclude empty lines and comment lines.
// skip and give a warning on non-absolute path.
// For user-default.list, auto prefix user's home dir, auto expand Documents, Downloads etc special folder.
File.WriteAllLines(mbackupList, allLines)
logger.Info "mbackup.list file written: %s" mbackupList
true
| (false, _) -> failwith "Read mbackup local.list file failed"
| (true, localListLines) ->
let userDefaultListLines = readMbackupListFile mbackupUserDefaultList |> Seq.map expandUserFile
let allLines = Seq.append (Seq.append defaultListLines localListLines) userDefaultListLines
// For mbackup-default.list and local.list, exclude empty lines and comment lines.
// skip and give a warning on non-absolute path.
// For user-default.list, auto prefix user's home dir, auto expand Documents, Downloads etc special folder.
File.WriteAllLines(mbackupList, allLines)
logger.Info "mbackup.list file written: %s" mbackupList
true
with
| :? System.IO.IOException as ex ->
logger.Error "Read/write file failed: %s %s" ex.Source ex.Message
false
| ex ->
logger.Error "Read/write mbackup list file failed: %s" ex.Message
false
| :? System.IO.IOException as ex ->
logger.Error "Read/write file failed: %s %s" ex.Source ex.Message
false
| ex ->
logger.Error "Read/write mbackup list file failed: %s" ex.Message
false
[<EntryPoint>]
let main argv =
......@@ -176,47 +178,56 @@ let main argv =
let sshPrivateKeyFile = results.GetResult(Ssh_Key, defaultValue = userHome + ".ssh/id_rsa") |> toMingwPath
let rsyncCmd = List.append rsyncCmd [sprintf "-e \"%s -F %s -i %s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\"" sshExeFile sshConfigFile sshPrivateKeyFile]
let mbackupConfig = WellsConfig(mbackupConfigFile)
let backupTargetMaybe = mbackupConfig.GetStr("target")
// precedence: command line argument > environment variable > config file
let normalizeTarget target =
if isLocalTarget target then
toMingwPath target
else
target
let backupTargetMaybe =
match results.TryGetResult Target with
| None ->
let mbackupConfig = WellsConfig(mbackupConfigFile)
let backupTargetMaybe = mbackupConfig.GetStr("target")
Option.map normalizeTarget backupTargetMaybe
| Some backupTarget ->
Some (normalizeTarget backupTarget)
match backupTargetMaybe with
| None ->
logger.Error "TARGET is not defined"
ExitBadParam
| Some backupTarget ->
let backupTarget = toMingwPath backupTarget
let rsyncCmd =
if not (isLocalTarget backupTarget)
then
let nodeName = results.GetResult(Node_Name, defaultValue = Net.Dns.GetHostName())
let remoteLogFile = sprintf "/var/log/mbackup/%s.log" nodeName
let remoteUser = results.GetResult (Remote_User, defaultValue = Environment.UserName)
let rsyncCmd = List.append rsyncCmd [sprintf "--remote-option=--log-file=%s" remoteLogFile]
let rsyncCmd = List.append rsyncCmd [sprintf "--chown=%s:%s" remoteUser remoteUser]
rsyncCmd
else
rsyncCmd
let rsyncCmd = List.append rsyncCmd ["/"]
let rsyncCmd = List.append rsyncCmd [backupTarget]
let rsyncArgs = rsyncCmd |> String.concat " "
let rsyncExe = mbackupInstallDirWinTest + "rsync-w64\\usr\\bin\\rsync.exe"
let echoExe = "C:\\Program Files\\Git\\usr\\bin\\echo.exe"
try
IO.Directory.CreateDirectory(runtimeDir) |> ignore
IO.Directory.CreateDirectory(userConfigDir) |> ignore
let proc = Process.Start(rsyncExe, rsyncArgs)
logger.Info "Note: if you run the following rsync command yourself, make sure the generated file list (%s) is up-to-date.\n%s" mbackupFile (rsyncExe + " " + rsyncArgs)
if proc.WaitForExit Int32.MaxValue then
logger.Info "mbackup exit"
proc.ExitCode
| None ->
logger.Error "TARGET is not defined"
ExitBadParam
| Some backupTarget ->
let rsyncCmd =
if not (isLocalTarget backupTarget)
then
let nodeName = results.GetResult(Node_Name, defaultValue = Net.Dns.GetHostName())
let remoteLogFile = sprintf "/var/log/mbackup/%s.log" nodeName
let remoteUser = results.GetResult (Remote_User, defaultValue = Environment.UserName)
let rsyncCmd = List.append rsyncCmd [sprintf "--remote-option=--log-file=%s" remoteLogFile]
let rsyncCmd = List.append rsyncCmd [sprintf "--chown=%s:%s" remoteUser remoteUser]
rsyncCmd
else
logger.Error "mbackup timed out while waiting for rsync to complete"
ExitTimeout
with
| :? System.IO.IOException as ex ->
logger.Error "IO Error: %s %s" ex.Source ex.Message
ExitIOError
| ex ->
logger.Error "Unexpected Error: %s" ex.Message
ExitIOError
rsyncCmd
let rsyncCmd = List.append rsyncCmd ["/"]
let rsyncCmd = List.append rsyncCmd [backupTarget]
let rsyncArgs = rsyncCmd |> String.concat " "
let rsyncExe = mbackupInstallDirWinTest + "rsync-w64\\usr\\bin\\rsync.exe"
let echoExe = "C:\\Program Files\\Git\\usr\\bin\\echo.exe"
try
IO.Directory.CreateDirectory(runtimeDir) |> ignore
IO.Directory.CreateDirectory(userConfigDir) |> ignore
let proc = Process.Start(rsyncExe, rsyncArgs)
logger.Info "Note: if you run the following rsync command yourself, make sure the generated file list (%s) is up-to-date.\n%s" mbackupFile (rsyncExe + " " + rsyncArgs)
if proc.WaitForExit Int32.MaxValue then
logger.Info "mbackup exit"
proc.ExitCode
else
logger.Error "mbackup timed out while waiting for rsync to complete"
ExitTimeout
with
| :? System.IO.IOException as ex ->
logger.Error "IO Error: %s %s" ex.Source ex.Message
ExitIOError
| ex ->
logger.Error "Unexpected Error: %s" ex.Message
ExitIOError
# example config file
# target=d:\backup\
# target=myhost.example.com:/data/backup/somedir/
......@@ -2,6 +2,7 @@ module MbackupTests
open NUnit.Framework
open Mbackup.Lib
open Mbackup.Program
[<SetUp>]
let Setup () =
......@@ -57,3 +58,14 @@ let TestStringSplit () =
let r = "a=b=c".Split('=', 2)
Assert.That("a", Is.EqualTo(r.[0]))
Assert.That("b=c", Is.EqualTo(r.[1]))
[<Test>]
let TestIsLocalTarget () =
Assert.That(isLocalTarget("D:\\backup"))
Assert.That(isLocalTarget("d:\\backup"))
Assert.That(isLocalTarget("C:\\backup"))
Assert.That(isLocalTarget("d:/backup"))
Assert.That(isLocalTarget("D:/backup"))
Assert.That(isLocalTarget("F:\\mbackup"))
Assert.That(isLocalTarget("/cygdrive/d/backup"))
Assert.That(isLocalTarget("/d/backup"))
......@@ -20,7 +20,7 @@ remote logging works.
read target from config file works. now I can just run
dotnet run -- -i
** 2019-11-12 docs
** 2019-11-12 docs :documents:
- rsync
https://www.samba.org/ftp/rsync/rsync.html
- Basic Editing in Visual Studio Code
......@@ -86,6 +86,16 @@ C:\ProgramData\mbackup\mbackup-default.list
C:\ProgramData\mbackup\user-default.list
C:\ProgramData\mbackup\mbackup.ini
** 2019-11-14 notes :development:
- Argu optional param support.
if option Target is optional, when try to get its value, you should use
results.TryGetResult Target
or
results.GetResult(Target, defaultValue = xxx)
-
* later :entry:
** 2019-11-14 supports expand Downloads dir in user-default.list
** 2019-11-14 vscode f# doesn't support open a module
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment