Skip to content
Commits on Source (3)
...@@ -27,6 +27,10 @@ type WellsConfig(fn: string) = ...@@ -27,6 +27,10 @@ type WellsConfig(fn: string) =
match getEnv (configKeyToEnvVar key) with match getEnv (configKeyToEnvVar key) with
| null | "" -> keyValuePairs.TryFind key | null | "" -> keyValuePairs.TryFind key
| envValue -> Some envValue | envValue -> Some envValue
member this.GetStrDefault key defaultValue =
match this.GetStr key with
| None -> defaultValue
| Some value -> value
member this.GetBool key = member this.GetBool key =
Option.map (fun value -> Regex.IsMatch(value, "^(yes|true|enable|1)$", RegexOptions.IgnoreCase)) (this.GetStr key) Option.map (fun value -> Regex.IsMatch(value, "^(yes|true|enable|1)$", RegexOptions.IgnoreCase)) (this.GetStr key)
member this.GetFloat key = member this.GetFloat key =
......
// Learn more about F# at http://fsharp.org // Learn more about F# at http://fsharp.org
// //
// - mbackup config file
// %programdata%/mbackup/mbackup-config.txt
// - backup file list // - backup file list
// /%appdata%/mbackup/mbackup-default.list // %programdata%/mbackup/default-list.txt
// /%appdata%/mbackup/user-default.list // %programdata%/mbackup/user-default-list.txt
// /%appdata%/mbackup/local.list (optional) // %programdata%/mbackup/local-list.txt (optional)
// - exclude pattern // - exclude pattern
// /%appdata%/mbackup/mbackup-default.exclude // %programdata%/mbackup/default-exclude.txt
// /%appdata%/mbackup/local.exclude (optional) // %programdata%/mbackup/local-exclude.txt (optional)
module Mbackup.Program module Mbackup.Program
...@@ -30,6 +32,20 @@ let ExitUserError = 4 ...@@ -30,6 +32,20 @@ let ExitUserError = 4
let version = Reflection.Assembly.GetEntryAssembly().GetName().Version let version = Reflection.Assembly.GetEntryAssembly().GetName().Version
let versionStr = version.ToString() let versionStr = version.ToString()
// base filename for use in mbackup for windows.
// I use .txt and .log extension because user can open/edit them easily.
module MbackupFileName =
let DefaultList = "default-list.txt"
let DefaultExclude = "default-exclude.txt"
let LocalList = "local-list.txt"
let LocalExclude = "local-exclude.txt"
let UserDefaultList = "user-default-list.txt"
let Config = "mbackup-config.txt"
// run time files
let GeneratedList = "mbackup-list.txt"
let Log = "mbackup.log"
[<SuppressMessage("*", "UnionCasesNames")>] [<SuppressMessage("*", "UnionCasesNames")>]
type CLIArguments = type CLIArguments =
| [<AltCommandLine("-n")>] Dry_Run | [<AltCommandLine("-n")>] Dry_Run
...@@ -50,6 +66,11 @@ type CLIArguments = ...@@ -50,6 +66,11 @@ type CLIArguments =
| Ssh_Key _ -> "ssh private key, used when backup to remote ssh node" | Ssh_Key _ -> "ssh private key, used when backup to remote ssh node"
| Version _ -> "show mbackup version and exit" | Version _ -> "show mbackup version and exit"
type MbackupRuntimeConfig =
{ Logger: Logger
Config: WellsConfig
Options: ParseResults<CLIArguments> }
let programFilesDirWin = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) |> ensureWinDir let programFilesDirWin = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) |> ensureWinDir
let programFilesDir = toMingwPath programFilesDirWin let programFilesDir = toMingwPath programFilesDirWin
let mbackupProgramDirWin = programFilesDirWin + "mbackup\\" let mbackupProgramDirWin = programFilesDirWin + "mbackup\\"
...@@ -74,7 +95,6 @@ let mbackupInstallDir = mbackupInstallDirWin |> toMingwPath ...@@ -74,7 +95,6 @@ let mbackupInstallDir = mbackupInstallDirWin |> toMingwPath
let userHomeWin = let userHomeWin =
getEnvDefault "HOME" (Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)) |> ensureWinDir getEnvDefault "HOME" (Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)) |> ensureWinDir
let userHome = userHomeWin |> toMingwPath let userHome = userHomeWin |> toMingwPath
let userConfigDirWin = programDataDirWin + "mbackup\\" let userConfigDirWin = programDataDirWin + "mbackup\\"
...@@ -82,10 +102,8 @@ let userConfigDir = programDataDir + "mbackup/" ...@@ -82,10 +102,8 @@ let userConfigDir = programDataDir + "mbackup/"
let runtimeDirWin = appDataLocalDirWin + "mbackup\\" let runtimeDirWin = appDataLocalDirWin + "mbackup\\"
let runtimeDir = appDataLocalDir + "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. // 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) let isLocalTarget (target: string) = target.StartsWith "/" || Regex.IsMatch(target, "^[a-z]:", RegexOptions.IgnoreCase)
// expand user file to mingw64 rsync supported path. // expand user file to mingw64 rsync supported path.
// abc -> /cygdrive/c/Users/<user>/abc // abc -> /cygdrive/c/Users/<user>/abc
...@@ -120,19 +138,20 @@ let expandUserFile (fn: string) = ...@@ -120,19 +138,20 @@ let expandUserFile (fn: string) =
if fn.StartsWith("/") then fn if fn.StartsWith("/") then fn
else userHome + fn else userHome + fn
// generate mbackup.list file // read mbackup list file
let generateMbackupList (logger: Logger) = let readMbackupListFile fn =
// TODO how to only regenerate if source file have changed? should I bundle GNU make with mbackup?
// just compare mbackup.list mtime with its source files?
let mbackupDefaultList = userConfigDirWin + "mbackup-default.list"
let mbackupLocalList = userConfigDirWin + "local.list"
let mbackupUserDefaultList = userConfigDirWin + "user-default.list"
let mbackupList = runtimeDirWin + "mbackup.list"
// local functions
let dropEmptyLinesAndComments lines = let dropEmptyLinesAndComments lines =
Seq.filter (fun (line: string) -> not (line.TrimStart().StartsWith("#") || line.TrimEnd().Equals(""))) lines Seq.filter (fun (line: string) -> not (line.TrimStart().StartsWith("#") || line.TrimEnd().Equals(""))) lines
let readMbackupListFile fn = File.ReadAllLines(fn) |> dropEmptyLinesAndComments File.ReadAllLines(fn) |> dropEmptyLinesAndComments
// generate MbackupFileName.GeneratedList file
let generateMbackupList (logger: Logger) =
// TODO how to only regenerate if source file have changed? should I bundle GNU make with mbackup?
// just compare MbackupFileName.GeneratedList mtime with its source files?
let mbackupDefaultList = userConfigDirWin + MbackupFileName.DefaultList
let mbackupLocalList = userConfigDirWin + MbackupFileName.LocalList
let mbackupUserDefaultList = userConfigDirWin + MbackupFileName.UserDefaultList
let mbackupList = runtimeDirWin + MbackupFileName.GeneratedList
try try
let defaultListLines = readMbackupListFile mbackupDefaultList |> Seq.map toMingwPath let defaultListLines = readMbackupListFile mbackupDefaultList |> Seq.map toMingwPath
...@@ -144,24 +163,22 @@ let generateMbackupList (logger: Logger) = ...@@ -144,24 +163,22 @@ let generateMbackupList (logger: Logger) =
with with
| :? FileNotFoundException -> (true, Seq.empty) | :? FileNotFoundException -> (true, Seq.empty)
| ex -> | ex ->
logger.Error "Read mbackupLocalList failed: %s" ex.Message logger.Error "Read mbackupLocalList %s failed: %s" mbackupLocalList ex.Message
(false, Seq.empty) (false, Seq.empty)
match localListLinesMaybe with match localListLinesMaybe with
| (false, _) -> failwith "Read mbackup local.list file failed" | (false, _) -> failwith "Read mbackupLocalList failed"
| (true, localListLines) -> | (true, localListLines) ->
let userDefaultListLines = readMbackupListFile mbackupUserDefaultList |> Seq.map expandUserFile let userDefaultListLines = readMbackupListFile mbackupUserDefaultList |> Seq.map expandUserFile
let allLines = Seq.append (Seq.append defaultListLines localListLines) userDefaultListLines let allLines = Seq.append (Seq.append defaultListLines localListLines) userDefaultListLines
// For mbackup-default.list and local.list, exclude empty lines and comment lines. // For DefaultList and LocalList, exclude empty lines and comment lines.
// skip and give a warning on non-absolute path. // TODO 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. // For UserDefaultList, auto prefix user's home dir, auto expand Documents, Downloads etc special folder.
Directory.CreateDirectory(runtimeDirWin) |> ignore Directory.CreateDirectory(runtimeDirWin) |> ignore
File.WriteAllLines(mbackupList, allLines) File.WriteAllLines(mbackupList, allLines)
logger.Info logger.Info "GeneratedList written: %s" mbackupList
"mbackup.list file written: %s"
mbackupList
true true
with with
| :? System.IO.IOException as ex -> | :? IOException as ex ->
logger.Error "Read/write file failed: %s %s" ex.Source ex.Message logger.Error "Read/write file failed: %s %s" ex.Source ex.Message
false false
| ex -> | ex ->
...@@ -170,10 +187,12 @@ let generateMbackupList (logger: Logger) = ...@@ -170,10 +187,12 @@ let generateMbackupList (logger: Logger) =
exception PrivateKeyNotFoundException of string 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 sshExeFile = mbackupProgramDir + "rsync-w64/usr/bin/ssh.exe"
let sshConfigFile = userHome + ".ssh/config" let sshConfigFile = userHome + ".ssh/config"
let sshPrivateKeyFile = results.GetResult(Ssh_Key, defaultValue = userHome + ".ssh/id_rsa") |> toMingwPath let sshKnownHostsFile = userHome + ".ssh/known_hosts"
let sshPrivateKeyFile = options.GetResult(Ssh_Key, rc.Config.GetStrDefault "ssh-key" (userHome + ".ssh/id_rsa")) |> toMingwPath
let sshPrivateKeyFileWin = toWinPath sshPrivateKeyFile let sshPrivateKeyFileWin = toWinPath sshPrivateKeyFile
if not (File.Exists(sshPrivateKeyFileWin)) then if not (File.Exists(sshPrivateKeyFileWin)) then
raise (PrivateKeyNotFoundException("ssh private key doesn't exist: " + sshPrivateKeyFileWin)) raise (PrivateKeyNotFoundException("ssh private key doesn't exist: " + sshPrivateKeyFileWin))
...@@ -184,55 +203,69 @@ let addOptionsForRemoteBackup (results: ParseResults<CLIArguments>) (logger: Log ...@@ -184,55 +203,69 @@ let addOptionsForRemoteBackup (results: ParseResults<CLIArguments>) (logger: Log
let rsyncCmd = let rsyncCmd =
List.append rsyncCmd List.append rsyncCmd
[ sprintf "-e \"'%s'%s -i %s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\"" sshExeFile [ sprintf "-e \"'%s'%s -i %s -o StrictHostKeyChecking=ask -o UserKnownHostsFile=%s\""
sshConfigFileOption sshPrivateKeyFile ] 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 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 "--remote-option=--log-file=%s" remoteLogFile ]
let rsyncCmd = List.append rsyncCmd [ sprintf "--chown=%s:%s" remoteUser remoteUser ] let rsyncCmd = List.append rsyncCmd [ sprintf "--chown=%s:%s" remoteUser remoteUser ]
rsyncCmd rsyncCmd
[<EntryPoint>] [<EntryPoint>]
let main argv = let main argv =
let errorHandler = ProcessExiter(colorizer = function ErrorCode.HelpText -> None | _ -> Some ConsoleColor.Red) let logger = Logger()
let options =
let parser = ArgumentParser.Create<CLIArguments>(programName = "mbackup.exe", errorHandler = errorHandler) let errorHandler = ProcessExiter(colorizer = function ErrorCode.HelpText -> None | _ -> Some ConsoleColor.Red)
let results = parser.Parse argv let parser = ArgumentParser.Create<CLIArguments>(programName = "mbackup.exe", errorHandler = errorHandler)
parser.Parse argv
if results.Contains Version then let rc = {
MbackupRuntimeConfig.Config =
let mbackupConfigFile = userConfigDirWin + MbackupFileName.Config
WellsConfig(mbackupConfigFile)
Logger = logger
Options = options
}
if options.Contains Version then
printfn "mbackup %s" versionStr printfn "mbackup %s" versionStr
Environment.Exit(ExitSuccess) 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 "user config dir: %s" userConfigDirWin
logger.Info "runtime dir: %s" runtimeDirWin logger.Info "runtime dir: %s" runtimeDirWin
logger.Debug "program dir: %s" mbackupProgramDirWin logger.Debug "program dir: %s" mbackupProgramDirWin
let rsyncCmd: string list = [] let rsyncCmd: string list = []
let rsyncCmd = appendWhen dryRun rsyncCmd "--dry-run" let rsyncCmd = appendWhen (options.Contains Dry_Run) rsyncCmd "--dry-run"
let rsyncCmd = appendWhen itemizeChanges rsyncCmd "-i" let rsyncCmd = appendWhen (options.Contains Itemize_Changes) rsyncCmd "-i"
let rsyncCmd = let rsyncCmd =
List.append rsyncCmd List.append rsyncCmd
("-h --stats -togr --delete --delete-excluded --ignore-missing-args".Split [| ' ' |] |> Array.toList) ("-h --stats -togr --delete --delete-excluded --ignore-missing-args".Split [| ' ' |] |> Array.toList)
let mbackupFile = runtimeDir + "mbackup.list" if not (generateMbackupList logger) then
if not (generateMbackupList logger) then failwith "Generate mbackup.list failed" failwith (sprintf "Generate %s failed" MbackupFileName.GeneratedList)
let rsyncCmd = List.append rsyncCmd [ sprintf "--files-from=%s" mbackupFile ] let generatedFileList = runtimeDir + MbackupFileName.GeneratedList
let rsyncCmd = List.append rsyncCmd [ sprintf "--files-from=%s" generatedFileList ]
let excludeFile = userConfigDir + "mbackup-default.exclude" let rsyncCmd = List.append rsyncCmd [ sprintf "--exclude-from=%s" (userConfigDir + MbackupFileName.DefaultExclude) ]
let rsyncCmd = List.append rsyncCmd [ sprintf "--exclude-from=%s" excludeFile ]
let localExcludeFile = userConfigDir + "local.exclude"
let rsyncCmd = appendWhen (IO.File.Exists localExcludeFile) rsyncCmd (sprintf "--exclude-from=%s" localExcludeFile)
let localLogFile = runtimeDir + "mbackup.log" let runtimeLocalExcludeFile = runtimeDir + MbackupFileName.LocalExclude
let rsyncCmd = List.append rsyncCmd [ sprintf "--log-file=%s" localLogFile ] let rsyncCmd =
let localExcludeFile = userConfigDir + MbackupFileName.LocalExclude
if File.Exists localExcludeFile then
let convertAbsPathToMingwStyle (line: string) =
if Regex.IsMatch(line, "[a-z]:", RegexOptions.IgnoreCase) then
toMingwPath line
else
line
let lines =
readMbackupListFile localExcludeFile
|> Seq.map convertAbsPathToMingwStyle
File.WriteAllLines(runtimeLocalExcludeFile, lines)
appendWhen (File.Exists localExcludeFile) rsyncCmd (sprintf "--exclude-from=%s" runtimeLocalExcludeFile)
let rsyncCmd = List.append rsyncCmd [ sprintf "--log-file=%s" (runtimeDir + MbackupFileName.Log) ]
// precedence: command line argument > environment variable > config file // precedence: command line argument > environment variable > config file
let normalizeTarget target = let normalizeTarget target =
...@@ -240,10 +273,9 @@ let main argv = ...@@ -240,10 +273,9 @@ let main argv =
else target else target
let backupTargetMaybe = let backupTargetMaybe =
match results.TryGetResult Target with match options.TryGetResult Target with
| None -> | None ->
let mbackupConfig = WellsConfig(mbackupConfigFile) let backupTargetMaybe = rc.Config.GetStr("target")
let backupTargetMaybe = mbackupConfig.GetStr("target")
Option.map normalizeTarget backupTargetMaybe Option.map normalizeTarget backupTargetMaybe
| Some backupTarget -> Some(normalizeTarget backupTarget) | Some backupTarget -> Some(normalizeTarget backupTarget)
...@@ -255,7 +287,7 @@ let main argv = ...@@ -255,7 +287,7 @@ let main argv =
try try
let rsyncCmd = let rsyncCmd =
if not (isLocalTarget backupTarget) then if not (isLocalTarget backupTarget) then
addOptionsForRemoteBackup results logger rsyncCmd addOptionsForRemoteBackup rc rsyncCmd
else else
rsyncCmd rsyncCmd
let rsyncCmd = List.append rsyncCmd [ "/" ] let rsyncCmd = List.append rsyncCmd [ "/" ]
...@@ -266,7 +298,7 @@ let main argv = ...@@ -266,7 +298,7 @@ let main argv =
Directory.CreateDirectory(userConfigDirWin) |> ignore Directory.CreateDirectory(userConfigDirWin) |> ignore
logger.Info logger.Info
"Note: if you run the following rsync command yourself, make sure the generated file list (%s) is up-to-date.\n%s" "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) generatedFileList (rsyncExe + " " + rsyncArgs)
let processStartInfo = let processStartInfo =
ProcessStartInfo( ProcessStartInfo(
FileName = rsyncExe, FileName = rsyncExe,
......
# files/dirs to exclude, for syntax, check rsync patterns
# This file is reserved when uninstall/upgrade mbackup.
# lines started with # are comments.
# example:
# *.o
# C:\foo\bar.iso
# local dirs to backup.
# This file is reserved when uninstall/upgrade mbackup.
# lines started with # are comments.
# example:
# C:\mydir
# D:\some dir\some file.doc
# example config file # example config file, lines start with # are comments.
# target=d:\backup\ # target=d:\backup\
# target=myhost.example.com:/data/backup/somedir/ # target=myhost.example.com:/data/backup/somedir/
# remote-user=foo
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<RuntimeIdentifiers>win10-x64</RuntimeIdentifiers> <RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>
<RootNamespace>Mbackup</RootNamespace> <RootNamespace>Mbackup</RootNamespace>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>0.2.3.0</Version> <Version>0.3.0.0</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
......
...@@ -14,58 +14,85 @@ ...@@ -14,58 +14,85 @@
<Directory Id="MBACKUP_DATA" Name="mbackup"/> <Directory Id="MBACKUP_DATA" Name="mbackup"/>
</Directory> </Directory>
<Directory Id="DesktopFolder" Name="Desktop" /> <Directory Id="DesktopFolder" Name="Desktop" />
<Directory Id="ProgramMenuFolder" /> <Directory Id="ProgramMenuFolder">
<Directory Id="mbackup_start_menu_dir" Name="mbackup" />
</Directory>
</Directory> </Directory>
<DirectoryRef Id="MBACKUP_DATA">
<Component Id="default_exclude.txt" Guid="*">
<File Id="default_exclude.txt" Source="mbackup-config\default-exclude.txt" KeyPath="yes"/>
</Component>
<Component Id="default_list.txt" Guid="*">
<File Id="default_list.txt" Source="mbackup-config\default-list.txt" KeyPath="yes"/>
</Component>
<Component Id="local_list.txt" NeverOverwrite="yes" Permanent="yes" Guid="*">
<!-- do not overwrite this component/file if it already exists on target system -->
<File Id="local_list.txt" Source="mbackup-config\local-list.txt" KeyPath="yes"/>
</Component>
<Component Id="local_exclude.txt" NeverOverwrite="yes" Permanent="yes" Guid="*">
<!-- do not overwrite this component/file if it already exists on target system -->
<File Id="local_exclude.txt" Source="mbackup-config\local-exclude.txt" KeyPath="yes"/>
</Component>
<Component Id="mbackup_config.txt" NeverOverwrite="yes" Permanent="yes" Guid="*">
<!-- do not overwrite this component/file if it already exists on target system -->
<File Id="mbackup_config.txt" Source="mbackup-config\mbackup-config.txt" KeyPath="yes"/>
</Component>
<Component Id="user_default_list.txt" Guid="*">
<File Id="user_default_list.txt" Source="mbackup-config\user-default-list.txt" KeyPath="yes"/>
</Component>
</DirectoryRef>
<DirectoryRef Id="DesktopFolder"> <DirectoryRef Id="DesktopFolder">
<Component Id="ApplicationShortcutDesktop" Guid="*"> <Component Id="mbackup_desktop_shortcuts" Guid="*">
<Shortcut Id="ApplicationDesktopShortcut" <Shortcut Id="DesktopMbackupExe"
Name="mbackup" Name="mbackup"
Description="Backup your computer using rsync" Description="Backup your computer using rsync"
Target="[#filE3FFB5E42FD6DDB7AD945F29409779F0]" Target="[!filE3FFB5E42FD6DDB7AD945F29409779F0]"
WorkingDirectory="MBACKUP_PROGRAM_FILES"/> WorkingDirectory="MBACKUP_PROGRAM_FILES"/>
<RemoveFolder Id="DesktopShortcut" On="uninstall"/> <RemoveFolder Id="DesktopShortcut" On="uninstall"/>
<RegistryValue Root="HKCU" Key="Software\mbackup" Name="installed" Type="integer" Value="1" KeyPath="yes"/> <RegistryValue Root="HKCU" Key="Software\mbackup\desktopshortcuts" Name="installed" Type="integer" Value="1" KeyPath="yes"/>
</Component> </Component>
</DirectoryRef> </DirectoryRef>
<DirectoryRef Id="ProgramMenuFolder"> <DirectoryRef Id="mbackup_start_menu_dir">
<Component Id="ApplicationShortcutStartMenu" Guid="F7C168BA-BDC9-4C4D-8AE7-722AC399AFD5"> <Component Id="mbackup_start_menu" Guid="F7C168BA-BDC9-4C4D-8AE7-722AC399AFD5">
<Shortcut Id="ApplicationStartMenuShortcut" <Shortcut Id="StartMenuMbackupExe"
Name="mbackup" Name="mbackup"
Description="Backup your computer using rsync" Description="Backup your computer using rsync"
Target="[#filE3FFB5E42FD6DDB7AD945F29409779F0]" Target="[!filE3FFB5E42FD6DDB7AD945F29409779F0]"
WorkingDirectory="MBACKUP_PROGRAM_FILES"/>
<Shortcut Id="StartMenuMbackupConfig"
Name="mbackup-config.txt"
Description="mbackup config file"
Target="[!mbackup_config.txt]"
WorkingDirectory="MBACKUP_PROGRAM_FILES"/>
<Shortcut Id="StartMenuLocalList"
Name="local-list.txt"
Description="mbackup local-list.txt file"
Target="[!local_list.txt]"
WorkingDirectory="MBACKUP_PROGRAM_FILES"/> WorkingDirectory="MBACKUP_PROGRAM_FILES"/>
<RemoveFolder Id="StartMenuShortCut" On="uninstall"/> <Shortcut Id="StartMenuLocalExclude"
<RegistryValue Root="HKCU" Key="Software\mbackup" Name="installed" Type="integer" Value="1" KeyPath="yes"/> Name="local-exclude.txt"
Description="mbackup local-exclude.txt file"
Target="[!local_exclude.txt]"
WorkingDirectory="MBACKUP_PROGRAM_FILES"/>
<RemoveFolder Id="MBACKUP_START_MENU_DIR" On="uninstall"/>
<RegistryValue Root="HKCU" Key="Software\mbackup\startmenushortcuts" Name="installed" Type="integer" Value="1" KeyPath="yes"/>
</Component> </Component>
</DirectoryRef> </DirectoryRef>
<DirectoryRef Id="MBACKUP_DATA">
<Component Id="mbackup_default.exclude" Guid="*">
<File Id="mbackup_default.exclude" Source="mbackup-config\mbackup-default.exclude" KeyPath="yes"/>
</Component>
<Component Id="mbackup_default.list" Guid="*">
<File Id="mbackup_default.list" Source="mbackup-config\mbackup-default.list" KeyPath="yes"/>
</Component>
<Component Id="user_default.list" Guid="*">
<File Id="user_default.list" Source="mbackup-config\user-default.list" KeyPath="yes"/>
</Component>
<Component Id="mbackup.txt" NeverOverwrite="yes" Permanent="yes" Guid="*">
<!-- do not overwrite this component/file if it already exists on target system -->
<File Id="mbackup.txt" Source="mbackup-config\mbackup.txt" KeyPath="yes"/>
</Component>
</DirectoryRef>
<Feature Id="MainApplication" Title="Main Application" Level="1"> <Feature Id="MainApplication" Title="Main Application" Level="1">
<ComponentGroupRef Id="MbackupHeatGenerated" /> <ComponentGroupRef Id="MbackupHeatGenerated" />
<ComponentGroupRef Id="RsyncHeatGenerated" /> <ComponentGroupRef Id="RsyncHeatGenerated" />
<ComponentRef Id="mbackup_default.exclude" /> <ComponentRef Id="default_exclude.txt" />
<ComponentRef Id="mbackup_default.list" /> <ComponentRef Id="default_list.txt" />
<ComponentRef Id="user_default.list" /> <ComponentRef Id="local_list.txt" />
<ComponentRef Id="mbackup.txt" /> <ComponentRef Id="local_exclude.txt" />
<ComponentRef Id="ApplicationShortcutStartMenu" /> <ComponentRef Id="mbackup_config.txt" />
<ComponentRef Id="ApplicationShortcutDesktop" /> <ComponentRef Id="user_default_list.txt" />
<ComponentRef Id="mbackup_start_menu" />
<ComponentRef Id="mbackup_desktop_shortcuts" />
</Feature> </Feature>
</Product> </Product>
</Wix> </Wix>
This diff is collapsed.