Compare commits
	
		
			2 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 407eeb2d1b | |||
| 8e7841b4cc | 
							
								
								
									
										52
									
								
								backup.py
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								backup.py
									
									
									
									
									
								
							@ -45,6 +45,16 @@ loglvls = {'critical': logging.CRITICAL,
 | 
				
			|||||||
### DEFAULT NAMESPACE ###
 | 
					### DEFAULT NAMESPACE ###
 | 
				
			||||||
dflt_ns = 'http://git.root2.io/r00t2/borgextend/'
 | 
					dflt_ns = 'http://git.root2.io/r00t2/borgextend/'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# In code, replace "day" with "daily" when constructing command.
 | 
				
			||||||
 | 
					policyperiods = (
 | 
				
			||||||
 | 
					    ('seconds', 'secondly'),
 | 
				
			||||||
 | 
					    ('minutes', 'minutely'),
 | 
				
			||||||
 | 
					    ('hours', 'hourly'),
 | 
				
			||||||
 | 
					    ('days', 'daily'),
 | 
				
			||||||
 | 
					    ('weeks', 'weekly'),
 | 
				
			||||||
 | 
					    ('months', 'monthly'),
 | 
				
			||||||
 | 
					    ('years', 'yearly'),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### THE GUTS ###
 | 
					### THE GUTS ###
 | 
				
			||||||
class Backup(object):
 | 
					class Backup(object):
 | 
				
			||||||
@ -127,9 +137,6 @@ class Backup(object):
 | 
				
			|||||||
            if not reponames:
 | 
					            if not reponames:
 | 
				
			||||||
                reponames = []
 | 
					                reponames = []
 | 
				
			||||||
            repos = []
 | 
					            repos = []
 | 
				
			||||||
            dfltRetention = None
 | 
					 | 
				
			||||||
            if server.attrib.get('pruneRetention') is not None:
 | 
					 | 
				
			||||||
                dfltRetention = isodate.parse_duration(server.attrib.get('pruneRetention'))
 | 
					 | 
				
			||||||
            for repo in server.findall('{0}repo'.format(self.ns)):
 | 
					            for repo in server.findall('{0}repo'.format(self.ns)):
 | 
				
			||||||
                if reponames and repo.attrib['name'] not in reponames:
 | 
					                if reponames and repo.attrib['name'] not in reponames:
 | 
				
			||||||
                    continue
 | 
					                    continue
 | 
				
			||||||
@ -164,11 +171,20 @@ class Backup(object):
 | 
				
			|||||||
                                r['plugins'][pname]['params'][paramname] = json.loads(param.text)
 | 
					                                r['plugins'][pname]['params'][paramname] = json.loads(param.text)
 | 
				
			||||||
                            else:
 | 
					                            else:
 | 
				
			||||||
                                r['plugins'][pname]['params'][paramname] = param.text
 | 
					                                r['plugins'][pname]['params'][paramname] = param.text
 | 
				
			||||||
                retention = repo.attrib.get('pruneRetention')
 | 
					                keepWithin = repo.find('{0}keepWithin'.format(self.ns))
 | 
				
			||||||
                if retention is not None:
 | 
					                if keepWithin is not None:
 | 
				
			||||||
                    r['retention'] = isodate.parse_duration(retention)
 | 
					                    if 'retention' not in r.keys():
 | 
				
			||||||
                else:
 | 
					                        r['retention'] = {}
 | 
				
			||||||
                    r['retention'] = dfltRetention
 | 
					                    r['retention']['last'] = isodate.parse_duration(keepWithin.text)
 | 
				
			||||||
 | 
					                keepLast = repo.find('{0}keepLast'.format(self.ns))
 | 
				
			||||||
 | 
					                if keepLast is not None:
 | 
				
			||||||
 | 
					                    for e, _ in policyperiods:
 | 
				
			||||||
 | 
					                        policy = keepLast.find('{0}{1}'.format(self.ns, e))
 | 
				
			||||||
 | 
					                        if policy is not None:
 | 
				
			||||||
 | 
					                            if 'retention' not in r.keys():
 | 
				
			||||||
 | 
					                                r['retention'] = {}
 | 
				
			||||||
 | 
					                            # This is safe. We validate the config.
 | 
				
			||||||
 | 
					                            r['retention'][e] = int(policy.text)
 | 
				
			||||||
                repos.append(r)
 | 
					                repos.append(r)
 | 
				
			||||||
            return(repos)
 | 
					            return(repos)
 | 
				
			||||||
        self.logger.debug('VARS (before args cleanup): {0}'.format(vars(self)))
 | 
					        self.logger.debug('VARS (before args cleanup): {0}'.format(vars(self)))
 | 
				
			||||||
@ -482,12 +498,8 @@ class Backup(object):
 | 
				
			|||||||
            _user = self.repos[server].get('user', pwd.getpwuid(os.geteuid()).pw_name)
 | 
					            _user = self.repos[server].get('user', pwd.getpwuid(os.geteuid()).pw_name)
 | 
				
			||||||
            for repo in self.repos[server]['repos']:
 | 
					            for repo in self.repos[server]['repos']:
 | 
				
			||||||
                if repo.get('retention') is None:
 | 
					                if repo.get('retention') is None:
 | 
				
			||||||
                    # No prune duration was set. Skip.
 | 
					                    # No prune retention was set. Skip.
 | 
				
			||||||
                    continue
 | 
					                    continue
 | 
				
			||||||
                if isinstance(repo['retention'], datetime.timedelta):
 | 
					 | 
				
			||||||
                    retentionSeconds = repo['retention'].total_seconds()
 | 
					 | 
				
			||||||
                else:  # it's an isodate.Duration
 | 
					 | 
				
			||||||
                    retentionSeconds = repo['retention'].totimedelta(datetime.datetime.now()).total_seconds()
 | 
					 | 
				
			||||||
                _loc_env = _env.copy()
 | 
					                _loc_env = _env.copy()
 | 
				
			||||||
                if 'password' not in repo:
 | 
					                if 'password' not in repo:
 | 
				
			||||||
                    print('Password not supplied for {0}:{1}.'.format(server, repo['name']))
 | 
					                    print('Password not supplied for {0}:{1}.'.format(server, repo['name']))
 | 
				
			||||||
@ -500,8 +512,18 @@ class Backup(object):
 | 
				
			|||||||
                        '--log-json',
 | 
					                        '--log-json',
 | 
				
			||||||
                        '--{0}'.format(self.args['loglevel']),
 | 
					                        '--{0}'.format(self.args['loglevel']),
 | 
				
			||||||
                        'prune',
 | 
					                        'prune',
 | 
				
			||||||
                        '--stats',
 | 
					                        '--stats']
 | 
				
			||||||
                        '--keep-secondly', int(retentionSeconds)]
 | 
					                # keepWithin
 | 
				
			||||||
 | 
					                if repo['retention'].get('last') is not None:
 | 
				
			||||||
 | 
					                    if isinstance(repo['retention'].get('last'), datetime.timedelta):  # it's a time.timedelta
 | 
				
			||||||
 | 
					                        retentionSeconds = repo['retention'].total_seconds()
 | 
				
			||||||
 | 
					                    else:  # it's an isodate.Duration
 | 
				
			||||||
 | 
					                        retentionSeconds = repo['retention'].totimedelta(datetime.datetime.now()).total_seconds()
 | 
				
			||||||
 | 
					                    _cmd.extend(['--keep-within',  '{0}H'.format(retentionSeconds / 60)])
 | 
				
			||||||
 | 
					                # keepLast
 | 
				
			||||||
 | 
					                for e, a in policyperiods:
 | 
				
			||||||
 | 
					                    if repo['retention'].get(e, 0) is not 0:
 | 
				
			||||||
 | 
					                        _cmd.extend(['--keep-{0}'.format(a), repo['retention'][e]])
 | 
				
			||||||
                if self.repos[server]['remote'].lower()[0] in ('1', 't'):
 | 
					                if self.repos[server]['remote'].lower()[0] in ('1', 't'):
 | 
				
			||||||
                    repo_tgt = '{0}@{1}'.format(_user, server)
 | 
					                    repo_tgt = '{0}@{1}'.format(_user, server)
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										23
									
								
								config.xsd
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								config.xsd
									
									
									
									
									
								
							@ -43,6 +43,7 @@
 | 
				
			|||||||
    </xs:simpleContent>
 | 
					    </xs:simpleContent>
 | 
				
			||||||
  </xs:complexType>
 | 
					  </xs:complexType>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <!-- START ROOT -->
 | 
					  <!-- START ROOT -->
 | 
				
			||||||
  <xs:element name="borg">
 | 
					  <xs:element name="borg">
 | 
				
			||||||
    <xs:complexType>
 | 
					    <xs:complexType>
 | 
				
			||||||
@ -56,6 +57,24 @@
 | 
				
			|||||||
              <xs:element name="repo" minOccurs="1" maxOccurs="unbounded">
 | 
					              <xs:element name="repo" minOccurs="1" maxOccurs="unbounded">
 | 
				
			||||||
                <xs:complexType>
 | 
					                <xs:complexType>
 | 
				
			||||||
                  <xs:choice minOccurs="1" maxOccurs="unbounded">
 | 
					                  <xs:choice minOccurs="1" maxOccurs="unbounded">
 | 
				
			||||||
 | 
					                    <!-- START RETENTION POLICY -->
 | 
				
			||||||
 | 
					                    <!-- This is used to specify the per-repo retention *period* for pruning. -->
 | 
				
			||||||
 | 
					                    <xs:element name="keepWithin" minOccurs="0" maxOccurs="1" type="xs:duration"/>
 | 
				
			||||||
 | 
					                    <!-- This is used to specify more advanced retentions. -->
 | 
				
			||||||
 | 
					                    <xs:element name="keepLast" minOccurs="0" maxOccurs="1">
 | 
				
			||||||
 | 
					                      <xs:complexType>
 | 
				
			||||||
 | 
					                        <xs:choice minOccurs="1" maxOccurs="7">
 | 
				
			||||||
 | 
					                          <xs:element name="seconds" minOccurs="0" maxOccurs="1" type="xs:positiveInteger"/>
 | 
				
			||||||
 | 
					                          <xs:element name="minutes" minOccurs="0" maxOccurs="1" type="xs:positiveInteger"/>
 | 
				
			||||||
 | 
					                          <xs:element name="hours" minOccurs="0" maxOccurs="1" type="xs:positiveInteger"/>
 | 
				
			||||||
 | 
					                          <xs:element name="days" minOccurs="0" maxOccurs="1" type="xs:positiveInteger"/>
 | 
				
			||||||
 | 
					                          <xs:element name="weeks" minOccurs="0" maxOccurs="1" type="xs:positiveInteger"/>
 | 
				
			||||||
 | 
					                          <xs:element name="months" minOccurs="0" maxOccurs="1" type="xs:positiveInteger"/>
 | 
				
			||||||
 | 
					                          <xs:element name="years" minOccurs="0" maxOccurs="1" type="xs:positiveInteger"/>
 | 
				
			||||||
 | 
					                        </xs:choice>
 | 
				
			||||||
 | 
					                      </xs:complexType>
 | 
				
			||||||
 | 
					                    </xs:element>
 | 
				
			||||||
 | 
					                    <!-- END RETENTION POLICY -->
 | 
				
			||||||
                    <!-- START PATH -->
 | 
					                    <!-- START PATH -->
 | 
				
			||||||
                    <xs:element name="path" minOccurs="1"
 | 
					                    <xs:element name="path" minOccurs="1"
 | 
				
			||||||
                                maxOccurs="unbounded" type="xs:anyURI"/>
 | 
					                                maxOccurs="unbounded" type="xs:anyURI"/>
 | 
				
			||||||
@ -100,8 +119,6 @@
 | 
				
			|||||||
                  <!-- This specifies if a repo is a "dummy" configuration.
 | 
					                  <!-- This specifies if a repo is a "dummy" configuration.
 | 
				
			||||||
                       Useful for testing and placeholder. -->
 | 
					                       Useful for testing and placeholder. -->
 | 
				
			||||||
                  <xs:attribute name="dummy" type="xs:boolean" use="optional" default="false"/>
 | 
					                  <xs:attribute name="dummy" type="xs:boolean" use="optional" default="false"/>
 | 
				
			||||||
                  <!-- This is used to specify the per-repo retention period for pruning. -->
 | 
					 | 
				
			||||||
                  <xs:attribute name="pruneRetention" type="xs:duration" use="optional"/>
 | 
					 | 
				
			||||||
                </xs:complexType>
 | 
					                </xs:complexType>
 | 
				
			||||||
                <xs:unique name="uniquePath">
 | 
					                <xs:unique name="uniquePath">
 | 
				
			||||||
                  <xs:selector xpath="path"/>
 | 
					                  <xs:selector xpath="path"/>
 | 
				
			||||||
@ -124,8 +141,6 @@
 | 
				
			|||||||
            <!-- This specifies if a server is a "dummy" configuration.
 | 
					            <!-- This specifies if a server is a "dummy" configuration.
 | 
				
			||||||
                 Useful for testing and placeholder. -->
 | 
					                 Useful for testing and placeholder. -->
 | 
				
			||||||
            <xs:attribute name="dummy" type="xs:boolean" use="optional" default="false"/>
 | 
					            <xs:attribute name="dummy" type="xs:boolean" use="optional" default="false"/>
 | 
				
			||||||
            <!-- This is used to specify the server-wide default retention period for pruning. -->
 | 
					 | 
				
			||||||
            <xs:attribute name="pruneRetention" type="xs:duration" use="optional"/>
 | 
					 | 
				
			||||||
          </xs:complexType>
 | 
					          </xs:complexType>
 | 
				
			||||||
        </xs:element>
 | 
					        </xs:element>
 | 
				
			||||||
        <!-- END SERVER -->
 | 
					        <!-- END SERVER -->
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,5 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8" ?>
 | 
					<?xml version="1.0" encoding="UTF-8" ?>
 | 
				
			||||||
        <repo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
					        <repo name="testrepo2"
 | 
				
			||||||
              xmlns="http://git.root2.io/r00t2/borgextend/"
 | 
					 | 
				
			||||||
              xsi:schemaLocation="http://git.root2.io/r00t2/borgextend/ http://git.r00t2.io/r00t2/borgextend/src/branch/master/config.xsd"
 | 
					 | 
				
			||||||
              name="testrepo2"
 | 
					 | 
				
			||||||
              password="AnotherSuperSecretPassword"
 | 
					              password="AnotherSuperSecretPassword"
 | 
				
			||||||
              dummy="true">
 | 
					              dummy="true">
 | 
				
			||||||
            <path>/dev/null</path>
 | 
					            <path>/dev/null</path>
 | 
				
			||||||
 | 
				
			|||||||
@ -12,17 +12,9 @@
 | 
				
			|||||||
        "dummy" = a boolean; if you need to create a "dummy" server, set this to "true".
 | 
					        "dummy" = a boolean; if you need to create a "dummy" server, set this to "true".
 | 
				
			||||||
                  It will *not* be parsed or executed upon.
 | 
					                  It will *not* be parsed or executed upon.
 | 
				
			||||||
                  It won't even be created by an init operation or show up in a repolist operation.
 | 
					                  It won't even be created by an init operation or show up in a repolist operation.
 | 
				
			||||||
        "pruneRetention" = an ISO 8601 duration to be used as the default rentention period during a prune operation.
 | 
					 | 
				
			||||||
    -->
 | 
					    -->
 | 
				
			||||||
    <!--
 | 
					
 | 
				
			||||||
        The pruneRetention attribute's format (ISO 8601 Duration) can be found here:
 | 
					    <server target="fq.dn.tld" remote="true" rsh="ssh -p 22" user="root">
 | 
				
			||||||
            https://en.wikipedia.org/wiki/ISO_8601#Durations).
 | 
					 | 
				
			||||||
        It must be positive. The "alternative"/"extended" variant (the one using colons) should be supported
 | 
					 | 
				
			||||||
        (but may be unpredictable).
 | 
					 | 
				
			||||||
        It is optional, but can be used as a fallback during a prune operation if a repo child does not specify one.
 | 
					 | 
				
			||||||
        The below example would prune anything older than 1 month, 2 minutes, and 30 seconds.
 | 
					 | 
				
			||||||
    -->
 | 
					 | 
				
			||||||
    <server target="fq.dn.tld" remote="true" rsh="ssh -p 22" user="root" pruneRetention="P1MT2M30S">
 | 
					 | 
				
			||||||
        <!-- You can (and probably will) have multiple repos for each server. -->
 | 
					        <!-- You can (and probably will) have multiple repos for each server. -->
 | 
				
			||||||
        <!--
 | 
					        <!--
 | 
				
			||||||
            "name" = the repository name.
 | 
					            "name" = the repository name.
 | 
				
			||||||
@ -30,20 +22,41 @@
 | 
				
			|||||||
                         to enter it interactively and securely.
 | 
					                         to enter it interactively and securely.
 | 
				
			||||||
            "dummy" = see server[@dummy] explanation.
 | 
					            "dummy" = see server[@dummy] explanation.
 | 
				
			||||||
            "compression" = see https://borgbackup.readthedocs.io/en/stable/usage/create.html (-C option)
 | 
					            "compression" = see https://borgbackup.readthedocs.io/en/stable/usage/create.html (-C option)
 | 
				
			||||||
            "pruneRetention" = an ISO 8601 duration to be used as the rentention period during a prune operation.
 | 
					 | 
				
			||||||
                               If not specified, uses ../server/[@pruneRetention].
 | 
					 | 
				
			||||||
                               If ../server/[@pruneRetention] was not specified, this repo will be skipped during a prune.
 | 
					 | 
				
			||||||
        -->
 | 
					        -->
 | 
				
			||||||
        <repo name="testrepo"
 | 
					        <repo name="testrepo"
 | 
				
			||||||
              password="SuperSecretPassword"
 | 
					              password="SuperSecretPassword"
 | 
				
			||||||
              compression="lzma,9"
 | 
					              compression="lzma,9">
 | 
				
			||||||
              pruneRetention="P1Y"><!-- One year. -->
 | 
					            <!--
 | 
				
			||||||
            <!-- Each path entry is a path to back up.
 | 
					                keepWithin specifies a flat time period for archive retention during a prune.
 | 
				
			||||||
 | 
					                The keepWithin attribute's format (ISO 8601 Duration) can be found here:
 | 
				
			||||||
 | 
					                    https://en.wikipedia.org/wiki/ISO_8601#Durations).
 | 
				
			||||||
 | 
					                It must be positive. The "alternative"/"extended" variant (the one using colons) should be supported
 | 
				
			||||||
 | 
					                (but may be unpredictable).
 | 
				
			||||||
 | 
					                It is optional. If no keepWithin or keepLast (below) is specified, this repo will be skipped during
 | 
				
			||||||
 | 
					                prune operations.
 | 
				
			||||||
 | 
					                The below example would prune anything older than 1 month, 2 minutes, and 30 seconds.
 | 
				
			||||||
 | 
					                If both keepWithin and keepLast is provided, keepLast only affects archives *before* the time period
 | 
				
			||||||
 | 
					                specified by keepWithin.
 | 
				
			||||||
 | 
					            -->
 | 
				
			||||||
 | 
					            <keepWithin>P1MT2M30S</keepWithin>
 | 
				
			||||||
 | 
					            <!--
 | 
				
			||||||
 | 
					                keepLast specifies a *policy* for retention.
 | 
				
			||||||
 | 
					                It can be used to specify explicit "N archives per Y period type". For example, the below retains the last
 | 
				
			||||||
 | 
					                archive for each month (going back 2 months), and the last archive each year for the last three years.
 | 
				
			||||||
 | 
					            -->
 | 
				
			||||||
 | 
					            <keepLast>
 | 
				
			||||||
 | 
					                <months>2</months>
 | 
				
			||||||
 | 
					                <years>3</years>
 | 
				
			||||||
 | 
					            </keepLast>
 | 
				
			||||||
 | 
					            <!--
 | 
				
			||||||
 | 
					                Each path entry is a path to back up.
 | 
				
			||||||
                See https://borgbackup.readthedocs.io/en/stable/usage/create.html
 | 
					                See https://borgbackup.readthedocs.io/en/stable/usage/create.html
 | 
				
			||||||
                Note that globbing, etc. is *disabled* for security reasons, so you will need to specify all
 | 
					                Note that globbing, etc. is *disabled* for security reasons, so you will need to specify all
 | 
				
			||||||
                 directories explicitly. -->
 | 
					                parent directories explicitly.
 | 
				
			||||||
 | 
					                Recursing is enabled, though.
 | 
				
			||||||
 | 
					            -->
 | 
				
			||||||
            <path>/a</path>
 | 
					            <path>/a</path>
 | 
				
			||||||
            <!-- Each exclude entry should be a subdirectory of a <path> (otherwise it wouldn't match, obviously). -->
 | 
					            <!-- Each exclude entry should be a subdirectory of a <path> (otherwise it wouldn't match). -->
 | 
				
			||||||
            <exclude>/a/b</exclude>
 | 
					            <exclude>/a/b</exclude>
 | 
				
			||||||
            <!-- Prep items are executed in non-guaranteed order (but are likely to be performed in order given).
 | 
					            <!-- Prep items are executed in non-guaranteed order (but are likely to be performed in order given).
 | 
				
			||||||
                 If you require them to be in a specific order, you should use a wrapper script and
 | 
					                 If you require them to be in a specific order, you should use a wrapper script and
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user