Loading ConfigParser.fs +4 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,10 @@ type WellsConfig(fn: string) = match getEnv (configKeyToEnvVar key) with | null | "" -> keyValuePairs.TryFind key | envValue -> Some envValue member this.GetStrDefault key defaultValue = match this.GetStr key with | None -> defaultValue | Some value -> value member this.GetBool key = Option.map (fun value -> Regex.IsMatch(value, "^(yes|true|enable|1)$", RegexOptions.IgnoreCase)) (this.GetStr key) member this.GetFloat key = Loading Program.fs +30 −26 Original line number Diff line number Diff line Loading @@ -50,6 +50,11 @@ type CLIArguments = | Ssh_Key _ -> "ssh private key, used when backup to remote ssh node" | Version _ -> "show mbackup version and exit" type MbackupRuntimeConfig = { Logger: Logger Config: WellsConfig Options: ParseResults<CLIArguments> } let programFilesDirWin = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) |> ensureWinDir let programFilesDir = toMingwPath programFilesDirWin let mbackupProgramDirWin = programFilesDirWin + "mbackup\\" Loading @@ -74,7 +79,6 @@ let mbackupInstallDir = mbackupInstallDirWin |> toMingwPath let userHomeWin = getEnvDefault "HOME" (Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)) |> ensureWinDir let userHome = userHomeWin |> toMingwPath let userConfigDirWin = programDataDirWin + "mbackup\\" Loading @@ -82,8 +86,6 @@ let userConfigDir = programDataDir + "mbackup/" let runtimeDirWin = appDataLocalDirWin + "mbackup\\" let runtimeDir = appDataLocalDir + "mbackup/" let mbackupConfigFile = userConfigDirWin + "mbackup.txt" // 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) Loading Loading @@ -161,7 +163,7 @@ let generateMbackupList (logger: Logger) = mbackupList true with | :? System.IO.IOException as ex -> | :? IOException as ex -> logger.Error "Read/write file failed: %s %s" ex.Source ex.Message false | ex -> Loading @@ -170,11 +172,12 @@ let generateMbackupList (logger: Logger) = exception PrivateKeyNotFoundException of string let addOptionsForRemoteBackup (results: ParseResults<CLIArguments>) (logger: Logger) (rsyncCmd: string list) = let addOptionsForRemoteBackup (rc: MbackupRuntimeConfig) (rsyncCmd: string list) = let options = rc.Options let sshExeFile = mbackupProgramDir + "rsync-w64/usr/bin/ssh.exe" let sshConfigFile = userHome + ".ssh/config" let sshKnownHostsFile = userHome + ".ssh/known_hosts" let sshPrivateKeyFile = results.GetResult(Ssh_Key, defaultValue = userHome + ".ssh/id_rsa") |> toMingwPath let sshPrivateKeyFile = options.GetResult(Ssh_Key, rc.Config.GetStrDefault "ssh-key" (userHome + ".ssh/id_rsa")) |> toMingwPath let sshPrivateKeyFileWin = toWinPath sshPrivateKeyFile if not (File.Exists(sshPrivateKeyFileWin)) then raise (PrivateKeyNotFoundException("ssh private key doesn't exist: " + sshPrivateKeyFileWin)) Loading @@ -188,37 +191,39 @@ let addOptionsForRemoteBackup (results: ParseResults<CLIArguments>) (logger: Log [ sprintf "-e \"'%s'%s -i %s -o StrictHostKeyChecking=ask -o UserKnownHostsFile=%s\"" sshExeFile sshConfigFileOption sshPrivateKeyFile sshKnownHostsFile] let nodeName = results.GetResult(Node_Name, defaultValue = Net.Dns.GetHostName()) let nodeName = options.GetResult(Node_Name, (rc.Config.GetStrDefault "node-name" (Net.Dns.GetHostName()))) let remoteLogFile = sprintf "/var/log/mbackup/%s.log" nodeName let remoteUser = results.GetResult(Remote_User, defaultValue = Environment.UserName) let remoteUser = options.GetResult(Remote_User, (rc.Config.GetStrDefault "remote-user" 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 [<EntryPoint>] let main argv = let logger = Logger() let options = 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 parser.Parse argv let rc = { MbackupRuntimeConfig.Config = let mbackupConfigFile = userConfigDirWin + "mbackup.txt" WellsConfig(mbackupConfigFile) Logger = logger Options = options } if results.Contains Version then if options.Contains Version then printfn "mbackup %s" versionStr Environment.Exit(ExitSuccess) let dryRun = results.Contains Dry_Run let itemizeChanges = results.Contains Itemize_Changes let logger = Logger() logger.Info "user config dir: %s" userConfigDirWin logger.Info "runtime dir: %s" runtimeDirWin logger.Debug "program dir: %s" mbackupProgramDirWin let rsyncCmd: string list = [] let rsyncCmd = appendWhen dryRun rsyncCmd "--dry-run" let rsyncCmd = appendWhen itemizeChanges rsyncCmd "-i" let rsyncCmd = appendWhen (options.Contains Dry_Run) rsyncCmd "--dry-run" let rsyncCmd = appendWhen (options.Contains Itemize_Changes) rsyncCmd "-i" let rsyncCmd = List.append rsyncCmd ("-h --stats -togr --delete --delete-excluded --ignore-missing-args".Split [| ' ' |] |> Array.toList) Loading @@ -241,10 +246,9 @@ let main argv = else target let backupTargetMaybe = match results.TryGetResult Target with match options.TryGetResult Target with | None -> let mbackupConfig = WellsConfig(mbackupConfigFile) let backupTargetMaybe = mbackupConfig.GetStr("target") let backupTargetMaybe = rc.Config.GetStr("target") Option.map normalizeTarget backupTargetMaybe | Some backupTarget -> Some(normalizeTarget backupTarget) Loading @@ -256,7 +260,7 @@ let main argv = try let rsyncCmd = if not (isLocalTarget backupTarget) then addOptionsForRemoteBackup results logger rsyncCmd addOptionsForRemoteBackup rc rsyncCmd else rsyncCmd let rsyncCmd = List.append rsyncCmd [ "/" ] Loading mbackup-config/mbackup.txt +2 −1 Original line number Diff line number Diff line # example config file # example config file, lines start with # are comments. # target=d:\backup\ # target=myhost.example.com:/data/backup/somedir/ # remote-user=foo mbackup.fsproj +1 −1 Original line number Diff line number Diff line Loading @@ -6,7 +6,7 @@ <RuntimeIdentifiers>win10-x64</RuntimeIdentifiers> <RootNamespace>Mbackup</RootNamespace> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> <Version>0.2.4.0</Version> <Version>0.2.6.0</Version> </PropertyGroup> <ItemGroup> Loading operational +3 −3 Original line number Diff line number Diff line Loading @@ -194,9 +194,6 @@ yes. I should support this. Both local.list and local.exclude. ** 2019-11-16 support reading remote-user=xxx option from config file. cli arg > env var > config file. same with target option. ** 2019-11-14 learn how to create an msi installer. - WiX Toolset https://wixtoolset.org/ Loading Loading @@ -687,6 +684,9 @@ same with target option. it contains hostname. - * done :entry: ** 2019-11-16 support reading remote-user=xxx option from config file. cli arg > env var > config file. same with target option. ** 2019-11-13 next todos; scheduled task command line. - DONE fix TODOs in F# code - DONE add rsync command and arguments for running in windows. Loading Loading
ConfigParser.fs +4 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,10 @@ type WellsConfig(fn: string) = match getEnv (configKeyToEnvVar key) with | null | "" -> keyValuePairs.TryFind key | envValue -> Some envValue member this.GetStrDefault key defaultValue = match this.GetStr key with | None -> defaultValue | Some value -> value member this.GetBool key = Option.map (fun value -> Regex.IsMatch(value, "^(yes|true|enable|1)$", RegexOptions.IgnoreCase)) (this.GetStr key) member this.GetFloat key = Loading
Program.fs +30 −26 Original line number Diff line number Diff line Loading @@ -50,6 +50,11 @@ type CLIArguments = | Ssh_Key _ -> "ssh private key, used when backup to remote ssh node" | Version _ -> "show mbackup version and exit" type MbackupRuntimeConfig = { Logger: Logger Config: WellsConfig Options: ParseResults<CLIArguments> } let programFilesDirWin = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) |> ensureWinDir let programFilesDir = toMingwPath programFilesDirWin let mbackupProgramDirWin = programFilesDirWin + "mbackup\\" Loading @@ -74,7 +79,6 @@ let mbackupInstallDir = mbackupInstallDirWin |> toMingwPath let userHomeWin = getEnvDefault "HOME" (Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)) |> ensureWinDir let userHome = userHomeWin |> toMingwPath let userConfigDirWin = programDataDirWin + "mbackup\\" Loading @@ -82,8 +86,6 @@ let userConfigDir = programDataDir + "mbackup/" let runtimeDirWin = appDataLocalDirWin + "mbackup\\" let runtimeDir = appDataLocalDir + "mbackup/" let mbackupConfigFile = userConfigDirWin + "mbackup.txt" // 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) Loading Loading @@ -161,7 +163,7 @@ let generateMbackupList (logger: Logger) = mbackupList true with | :? System.IO.IOException as ex -> | :? IOException as ex -> logger.Error "Read/write file failed: %s %s" ex.Source ex.Message false | ex -> Loading @@ -170,11 +172,12 @@ let generateMbackupList (logger: Logger) = exception PrivateKeyNotFoundException of string let addOptionsForRemoteBackup (results: ParseResults<CLIArguments>) (logger: Logger) (rsyncCmd: string list) = let addOptionsForRemoteBackup (rc: MbackupRuntimeConfig) (rsyncCmd: string list) = let options = rc.Options let sshExeFile = mbackupProgramDir + "rsync-w64/usr/bin/ssh.exe" let sshConfigFile = userHome + ".ssh/config" let sshKnownHostsFile = userHome + ".ssh/known_hosts" let sshPrivateKeyFile = results.GetResult(Ssh_Key, defaultValue = userHome + ".ssh/id_rsa") |> toMingwPath let sshPrivateKeyFile = options.GetResult(Ssh_Key, rc.Config.GetStrDefault "ssh-key" (userHome + ".ssh/id_rsa")) |> toMingwPath let sshPrivateKeyFileWin = toWinPath sshPrivateKeyFile if not (File.Exists(sshPrivateKeyFileWin)) then raise (PrivateKeyNotFoundException("ssh private key doesn't exist: " + sshPrivateKeyFileWin)) Loading @@ -188,37 +191,39 @@ let addOptionsForRemoteBackup (results: ParseResults<CLIArguments>) (logger: Log [ sprintf "-e \"'%s'%s -i %s -o StrictHostKeyChecking=ask -o UserKnownHostsFile=%s\"" sshExeFile sshConfigFileOption sshPrivateKeyFile sshKnownHostsFile] let nodeName = results.GetResult(Node_Name, defaultValue = Net.Dns.GetHostName()) let nodeName = options.GetResult(Node_Name, (rc.Config.GetStrDefault "node-name" (Net.Dns.GetHostName()))) let remoteLogFile = sprintf "/var/log/mbackup/%s.log" nodeName let remoteUser = results.GetResult(Remote_User, defaultValue = Environment.UserName) let remoteUser = options.GetResult(Remote_User, (rc.Config.GetStrDefault "remote-user" 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 [<EntryPoint>] let main argv = let logger = Logger() let options = 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 parser.Parse argv let rc = { MbackupRuntimeConfig.Config = let mbackupConfigFile = userConfigDirWin + "mbackup.txt" WellsConfig(mbackupConfigFile) Logger = logger Options = options } if results.Contains Version then if options.Contains Version then printfn "mbackup %s" versionStr Environment.Exit(ExitSuccess) let dryRun = results.Contains Dry_Run let itemizeChanges = results.Contains Itemize_Changes let logger = Logger() logger.Info "user config dir: %s" userConfigDirWin logger.Info "runtime dir: %s" runtimeDirWin logger.Debug "program dir: %s" mbackupProgramDirWin let rsyncCmd: string list = [] let rsyncCmd = appendWhen dryRun rsyncCmd "--dry-run" let rsyncCmd = appendWhen itemizeChanges rsyncCmd "-i" let rsyncCmd = appendWhen (options.Contains Dry_Run) rsyncCmd "--dry-run" let rsyncCmd = appendWhen (options.Contains Itemize_Changes) rsyncCmd "-i" let rsyncCmd = List.append rsyncCmd ("-h --stats -togr --delete --delete-excluded --ignore-missing-args".Split [| ' ' |] |> Array.toList) Loading @@ -241,10 +246,9 @@ let main argv = else target let backupTargetMaybe = match results.TryGetResult Target with match options.TryGetResult Target with | None -> let mbackupConfig = WellsConfig(mbackupConfigFile) let backupTargetMaybe = mbackupConfig.GetStr("target") let backupTargetMaybe = rc.Config.GetStr("target") Option.map normalizeTarget backupTargetMaybe | Some backupTarget -> Some(normalizeTarget backupTarget) Loading @@ -256,7 +260,7 @@ let main argv = try let rsyncCmd = if not (isLocalTarget backupTarget) then addOptionsForRemoteBackup results logger rsyncCmd addOptionsForRemoteBackup rc rsyncCmd else rsyncCmd let rsyncCmd = List.append rsyncCmd [ "/" ] Loading
mbackup-config/mbackup.txt +2 −1 Original line number Diff line number Diff line # example config file # example config file, lines start with # are comments. # target=d:\backup\ # target=myhost.example.com:/data/backup/somedir/ # remote-user=foo
mbackup.fsproj +1 −1 Original line number Diff line number Diff line Loading @@ -6,7 +6,7 @@ <RuntimeIdentifiers>win10-x64</RuntimeIdentifiers> <RootNamespace>Mbackup</RootNamespace> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> <Version>0.2.4.0</Version> <Version>0.2.6.0</Version> </PropertyGroup> <ItemGroup> Loading
operational +3 −3 Original line number Diff line number Diff line Loading @@ -194,9 +194,6 @@ yes. I should support this. Both local.list and local.exclude. ** 2019-11-16 support reading remote-user=xxx option from config file. cli arg > env var > config file. same with target option. ** 2019-11-14 learn how to create an msi installer. - WiX Toolset https://wixtoolset.org/ Loading Loading @@ -687,6 +684,9 @@ same with target option. it contains hostname. - * done :entry: ** 2019-11-16 support reading remote-user=xxx option from config file. cli arg > env var > config file. same with target option. ** 2019-11-13 next todos; scheduled task command line. - DONE fix TODOs in F# code - DONE add rsync command and arguments for running in windows. Loading