diff --git a/Program.fs b/Program.fs index 2e7f58052655b4b6577b1162994dd8bff19b6e91..dd10a5faca98e38388c4403a5f541138a3675810 100644 --- a/Program.fs +++ b/Program.fs @@ -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//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 [] 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 diff --git a/mbackup-config/mbackup.txt b/mbackup-config/mbackup.txt new file mode 100644 index 0000000000000000000000000000000000000000..b0876747ae70ab94f59661a9b621f4d190370362 --- /dev/null +++ b/mbackup-config/mbackup.txt @@ -0,0 +1,3 @@ +# example config file +# target=d:\backup\ +# target=myhost.example.com:/data/backup/somedir/ diff --git a/mbackup-tests/MbackupTest.fs b/mbackup-tests/MbackupTest.fs index a080e106e94d33bd583ee082ae65d34da7686f44..60db4676af511067305d40fd053f1f3166260e21 100644 --- a/mbackup-tests/MbackupTest.fs +++ b/mbackup-tests/MbackupTest.fs @@ -2,6 +2,7 @@ module MbackupTests open NUnit.Framework open Mbackup.Lib +open Mbackup.Program [] 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])) + +[] +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")) diff --git a/operational b/operational index 8cf35dd286196c6a406733705a63e6f32b406221..0aa7687d86d50284cde47133bfb8eae3c0e80f5e 100644 --- a/operational +++ b/operational @@ -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