diff --git a/.gitignore b/.gitignore
index ff3b78a..fd69eda 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,12 @@ __pycache__/
*.sqlite3
*.deb
.idea/
+files/*
+repo/*
+repo\:testrepo/*
+config
+hints.21
+index.21
+integrity.21
+nonce
+README
\ No newline at end of file
diff --git a/backup.py b/backup.py
index 7c6b934..f14dc79 100755
--- a/backup.py
+++ b/backup.py
@@ -20,6 +20,7 @@ import re
import subprocess
import sys
import tempfile
+from xmlrpc.client import Server
# TODO: virtual env?
from lxml import etree # A lot safer and easier to use than the stdlib xml module.
try:
@@ -452,6 +453,79 @@ class Backup(object):
objPrinter(r, indent = 3)
print()
return()
+
+ def prune(self):
+ # TODO: support "--strip-components N"?
+ self.logger.info('START: prune')
+ for server in self.repos:
+ _env = os.environ.copy()
+ if self.repos[server]['remote'].lower()[0] in ('1', 't'):
+ _env['BORG_RSH'] = self.repos[server].get('rsh', None)
+ _env['LANG'] = 'en_US.UTF-8'
+ _env['LC_CTYPE'] = 'en_US.UTF-8'
+ _user = self.repos[server].get('user', pwd.getpwuid(os.geteuid()).pw_name)
+ for repo in self.repos[server]['repos']:
+ _loc_env = _env.copy()
+ if 'password' not in repo:
+ print('Password not supplied for {0}:{1}.'.format(server, repo['name']))
+ _loc_env['BORG_PASSPHRASE'] = getpass.getpass('Password (will NOT echo back): ')
+ else:
+ _loc_env['BORG_PASSPHRASE'] = repo['password']
+ self.logger.info('[{0}]: BEGIN PRUNE: {1}'.format(server, repo['name']))
+ # This is where we actually do the thing.
+ _cmd = [self.borgbin,
+ '--log-json',
+ '--{0}'.format(self.args['loglevel']),
+ 'prune',
+ '--stats']
+ if self.repos[server]['keepYearly'][0].isnumeric() and int(self.repos[server]['keepYearly'][0]) > 0:
+ _cmd.extend(['--keep-yearly', self.repos[server]['keepYearly'].lower()[0]])
+ if self.repos[server]['keepMonthly'][0].isnumeric() and int(self.repos[server]['keepMonthly'][0]) > 0:
+ _cmd.extend(['--keep-monthly', self.repos[server]['keepMonthly'].lower()[0]])
+ if self.repos[server]['keepWeekly'][0].isnumeric() and int(self.repos[server]['keepWeekly'][0]) > 0:
+ _cmd.extend(['--keep-weekly', self.repos[server]['keepWeekly'].lower()[0]])
+ if self.repos[server]['keepDaily'][0].isnumeric() and int(self.repos[server]['keepDaily'][0]) > 0:
+ _cmd.extend(['--keep-daily', self.repos[server]['keepDaily'].lower()[0]])
+ if self.repos[server]['keepHourly'][0].isnumeric() and int(self.repos[server]['keepHourly'][0]) > 0:
+ _cmd.extend(['--keep-hourly', self.repos[server]['keepHourly'].lower()[0]])
+ if self.repos[server]['keepMinutely'][0].isnumeric() and int(self.repos[server]['keepMinutely'][0]) > 0:
+ _cmd.extend(['--keep-minutely', self.repos[server]['keepMinutely'].lower()[0]])
+ if self.repos[server]['keepSecondly'][0].isnumeric() and int(self.repos[server]['keepSecondly'][0]) > 0:
+ _cmd.extend(['--keep-secondly', self.repos[server]['keepSecondly'].lower()[0]])
+ if self.repos[server]['remote'].lower()[0] in ('1', 't'):
+ repo_tgt = '{0}@{1}'.format(_user, server)
+ else:
+ repo_tgt = os.path.abspath(os.path.expanduser(server))
+ _cmd.append('{0}:{1}'.format(repo_tgt,
+ repo['name']))
+ self.logger.debug('VARS: {0}'.format(vars()))
+ # We don't use self.cmdExec() here because we want to explicitly
+ # pass the env and format the log line differently.
+ self.logger.debug('[{0}]: Running command: {1}'.format(repo['name'],
+ ' '.join(_cmd)))
+ if not self.args['dryrun']:
+ _out = subprocess.run(_cmd,
+ env = _loc_env,
+ stdout = subprocess.PIPE,
+ stderr = subprocess.PIPE)
+ _stdout = _out.stdout.decode('utf-8').strip()
+ _stderr = _out.stderr.decode('utf-8').strip()
+ _returncode = _out.returncode
+ self.logger.debug('[{0}]: (RESULT) {1}'.format(repo['name'], _stdout))
+ self.logger.debug('[{0}]: STDERR: ({2})\n{1}'.format(repo['name'],
+ _stderr,
+ ' '.join(
+ _cmd)))
+ if _returncode != 0:
+ self.logger.error(
+ '[{0}]: FAILED: {1}'.format(repo['name'], ' '.join(_cmd)))
+ if _stderr != '' and self.cron and _returncode != 0:
+ self.logger.warning('Command {0} failed: {1}'.format(' '.join(_cmd),
+ _stderr))
+ del (_loc_env['BORG_PASSPHRASE'])
+ self.logger.info('[{0}]: END PRUNE'.format(repo['name']))
+ self.logger.info('END: prune')
+ return()
def printer(self):
# TODO: better alignment. https://stackoverflow.com/a/5676884
@@ -684,6 +758,11 @@ def parseArgs():
parents = [commonargs,
remoteargs,
fileargs])
+ pruneargs = subparsers.add_parser('prune',
+ help = ('Prune backups to schedule.'),
+ parents = [commonargs,
+ remoteargs,
+ fileargs])
cvrtargs = subparsers.add_parser('convert',
help = ('Convert the legacy JSON format to the new XML format and quit'))
### OPERATION-SPECIFIC OPTIONS ###
@@ -837,6 +916,8 @@ def main():
bak.createRepo()
elif args['oper'] == 'restore':
bak.restore()
+ elif args['oper'] == 'prune':
+ bak.prune()
return()
diff --git a/config.xml b/config.xml
new file mode 100644
index 0000000..c7023e4
--- /dev/null
+++ b/config.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+ /home/nosbig/git-repos/nosbig/BorgExtend/files
+
+ /home/nosbig/git-repos/nosbig/BorgExtend/files/b.txt
+
+
+
+
+
diff --git a/config.xsd b/config.xsd
index 25d0130..c1cdeed 100644
--- a/config.xsd
+++ b/config.xsd
@@ -118,6 +118,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+