Merge branch 'development'
This commit is contained in:
		@@ -2,12 +2,12 @@
 | 
				
			|||||||
.SH NAME
 | 
					.SH NAME
 | 
				
			||||||
simple_backup \- Backup files and folders using rsync
 | 
					simple_backup \- Backup files and folders using rsync
 | 
				
			||||||
.SH SYNOPSIS
 | 
					.SH SYNOPSIS
 | 
				
			||||||
.BR simple_backup
 | 
					.B simple_backup
 | 
				
			||||||
\-h, \-\-help
 | 
					\-h, \-\-help
 | 
				
			||||||
.PD 0
 | 
					.PD 0
 | 
				
			||||||
.P
 | 
					.P
 | 
				
			||||||
.PD
 | 
					.PD
 | 
				
			||||||
.BR simple_backup
 | 
					.B simple_backup
 | 
				
			||||||
[\-c, \-\-config FILE]
 | 
					[\-c, \-\-config FILE]
 | 
				
			||||||
[\-i, \-\-input INPUT [INPUT...]]
 | 
					[\-i, \-\-input INPUT [INPUT...]]
 | 
				
			||||||
[\-o, \-\-output DIR]
 | 
					[\-o, \-\-output DIR]
 | 
				
			||||||
@@ -16,8 +16,8 @@ simple_backup \- Backup files and folders using rsync
 | 
				
			|||||||
.PD
 | 
					.PD
 | 
				
			||||||
.RS 14 [\-e, \-\-exclude FILE|DIR|PATTERN [FILE|...]]
 | 
					.RS 14 [\-e, \-\-exclude FILE|DIR|PATTERN [FILE|...]]
 | 
				
			||||||
[\-k, \-\-keep N]
 | 
					[\-k, \-\-keep N]
 | 
				
			||||||
[\-\-host HOSTNAME]
 | 
					[\-\-ssh\-host HOSTNAME]
 | 
				
			||||||
[\-u, \-\-username USERNAME]
 | 
					[\-\-ssh\-user USERNAME]
 | 
				
			||||||
[\-\-keyfile FILE]
 | 
					[\-\-keyfile FILE]
 | 
				
			||||||
.PD 0
 | 
					.PD 0
 | 
				
			||||||
.P
 | 
					.P
 | 
				
			||||||
@@ -27,7 +27,7 @@ simple_backup \- Backup files and folders using rsync
 | 
				
			|||||||
[\-\-remove\-before\-backup]
 | 
					[\-\-remove\-before\-backup]
 | 
				
			||||||
.RE
 | 
					.RE
 | 
				
			||||||
.SH DESCRIPTION
 | 
					.SH DESCRIPTION
 | 
				
			||||||
.BR simple_backup
 | 
					.B simple_backup
 | 
				
			||||||
is a python script for performing backup of files and folders.
 | 
					is a python script for performing backup of files and folders.
 | 
				
			||||||
.P
 | 
					.P
 | 
				
			||||||
It uses rsync to copy the files to the specified location. Parameters for the backup such as
 | 
					It uses rsync to copy the files to the specified location. Parameters for the backup such as
 | 
				
			||||||
@@ -62,10 +62,10 @@ will not be copied. Multiple elements can be specified, in the same way as for t
 | 
				
			|||||||
Specify how many old backups (so excluding the current one) will be kept. The default behavior
 | 
					Specify how many old backups (so excluding the current one) will be kept. The default behavior
 | 
				
			||||||
is to keep them all (same as N=\-1)
 | 
					is to keep them all (same as N=\-1)
 | 
				
			||||||
.TP
 | 
					.TP
 | 
				
			||||||
.B \-\-host HOSTNAME
 | 
					.B \-\-ssh\-host HOSTNAME
 | 
				
			||||||
Hostname of the server where to copy the files in case of remote backup through SSH
 | 
					Hostname of the server where to copy the files in case of remote backup through SSH
 | 
				
			||||||
.TP
 | 
					.TP
 | 
				
			||||||
.B \-u, \-\-username USERNAME
 | 
					.B \-\-ssh\-user USERNAME
 | 
				
			||||||
Username for connecting to the server in case of remote backup
 | 
					Username for connecting to the server in case of remote backup
 | 
				
			||||||
.TP
 | 
					.TP
 | 
				
			||||||
.B \-\-keyfile FILE
 | 
					.B \-\-keyfile FILE
 | 
				
			||||||
@@ -104,7 +104,7 @@ Options \-r and \-v are used in any case. Not that options must be specified wit
 | 
				
			|||||||
.EE
 | 
					.EE
 | 
				
			||||||
.TP
 | 
					.TP
 | 
				
			||||||
Check
 | 
					Check
 | 
				
			||||||
.B rsync (1)
 | 
					.BR rsync (1)
 | 
				
			||||||
for details about the options.
 | 
					for details about the options.
 | 
				
			||||||
.RE
 | 
					.RE
 | 
				
			||||||
.TP
 | 
					.TP
 | 
				
			||||||
@@ -123,7 +123,7 @@ Copy it to the default location ($HOME/.config/simple_backup) and edit it as nee
 | 
				
			|||||||
.SH REMOTE BACKUP
 | 
					.SH REMOTE BACKUP
 | 
				
			||||||
It is possible to choose a directory on a remote server as destination for the backup. The files
 | 
					It is possible to choose a directory on a remote server as destination for the backup. The files
 | 
				
			||||||
are copied by rsync through SSH. Server hostname and username must be specified, either in the
 | 
					are copied by rsync through SSH. Server hostname and username must be specified, either in the
 | 
				
			||||||
configuration file, or on the command line (\(aq\-\-host\(aq and \(aq\-\-username\(aq options).
 | 
					configuration file, or on the command line (\(aq\-\-ssh\-host\(aq and \(aq\-\-ssh\-user\(aq options).
 | 
				
			||||||
.SS AUTHENTICATION
 | 
					.SS AUTHENTICATION
 | 
				
			||||||
For authentication, it is possible to use SSH key or password.
 | 
					For authentication, it is possible to use SSH key or password.
 | 
				
			||||||
.P
 | 
					.P
 | 
				
			||||||
@@ -145,7 +145,7 @@ in order to connect to the user\(aq s SSH agent it is necessary to preserve the
 | 
				
			|||||||
It is also possible to make this permanent by editing the
 | 
					It is also possible to make this permanent by editing the
 | 
				
			||||||
.B sudoers
 | 
					.B sudoers
 | 
				
			||||||
file (see
 | 
					file (see
 | 
				
			||||||
.B sudoers (5)
 | 
					.BR sudoers (5)
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
.P
 | 
					.P
 | 
				
			||||||
If SSH key authentication is not available, password authentication will be used instead.
 | 
					If SSH key authentication is not available, password authentication will be used instead.
 | 
				
			||||||
@@ -154,7 +154,7 @@ Note that in this case
 | 
				
			|||||||
(if available) will be used to send the password to rsync, to avoid prompting the user for
 | 
					(if available) will be used to send the password to rsync, to avoid prompting the user for
 | 
				
			||||||
the password multiple
 | 
					the password multiple
 | 
				
			||||||
times. This can pose some security risks, see
 | 
					times. This can pose some security risks, see
 | 
				
			||||||
.B sshpass (1)
 | 
					.BR sshpass (1)
 | 
				
			||||||
for details. For this reason, use SSH key authentication if possible.
 | 
					for details. For this reason, use SSH key authentication if possible.
 | 
				
			||||||
.SH EXIT STATUS
 | 
					.SH EXIT STATUS
 | 
				
			||||||
.TP
 | 
					.TP
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,8 +15,8 @@ keep=-1
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Uncomment the following section to enable backup to remote server through ssh
 | 
					# Uncomment the following section to enable backup to remote server through ssh
 | 
				
			||||||
# [server]
 | 
					# [server]
 | 
				
			||||||
# host=
 | 
					# ssh_host=
 | 
				
			||||||
# username=
 | 
					# ssh_user=
 | 
				
			||||||
# ssh_keyfile=
 | 
					# ssh_keyfile=
 | 
				
			||||||
# remote_sudo=
 | 
					# remote_sudo=
 | 
				
			||||||
# numeric_ids=
 | 
					# numeric_ids=
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ Classes:
 | 
				
			|||||||
    MyFormatter
 | 
					    MyFormatter
 | 
				
			||||||
    Backup
 | 
					    Backup
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Import libraries
 | 
					# Import libraries
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
@@ -33,7 +34,6 @@ from dotenv import load_dotenv
 | 
				
			|||||||
warnings.filterwarnings('error')
 | 
					warnings.filterwarnings('error')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
try:
 | 
					try:
 | 
				
			||||||
    raise ImportError
 | 
					 | 
				
			||||||
    import paramiko
 | 
					    import paramiko
 | 
				
			||||||
    from paramiko import RSAKey, Ed25519Key, ECDSAKey, DSSKey
 | 
					    from paramiko import RSAKey, Ed25519Key, ECDSAKey, DSSKey
 | 
				
			||||||
except ImportError:
 | 
					except ImportError:
 | 
				
			||||||
@@ -119,9 +119,9 @@ class Backup:
 | 
				
			|||||||
            String representing main backup options for rsync
 | 
					            String representing main backup options for rsync
 | 
				
			||||||
        keep: int
 | 
					        keep: int
 | 
				
			||||||
            Number of old backup to preserve
 | 
					            Number of old backup to preserve
 | 
				
			||||||
        host: str
 | 
					        ssh_host: str
 | 
				
			||||||
            Hostname of server (for remote backup)
 | 
					            Hostname of server (for remote backup)
 | 
				
			||||||
        username: str
 | 
					        ssh_user: str
 | 
				
			||||||
            Username for server login (for remote backup)
 | 
					            Username for server login (for remote backup)
 | 
				
			||||||
        ssh_keyfile: str
 | 
					        ssh_keyfile: str
 | 
				
			||||||
            Location of ssh key
 | 
					            Location of ssh key
 | 
				
			||||||
@@ -143,15 +143,15 @@ class Backup:
 | 
				
			|||||||
            Perform the backup
 | 
					            Perform the backup
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, inputs, output, exclude, keep, options, host=None, username=None,
 | 
					    def __init__(self, inputs, output, exclude, keep, options, ssh_host=None, ssh_user=None,
 | 
				
			||||||
                 ssh_keyfile=None, remote_sudo=False, remove_before=False):
 | 
					                 ssh_keyfile=None, remote_sudo=False, remove_before=False):
 | 
				
			||||||
        self.inputs = inputs
 | 
					        self.inputs = inputs
 | 
				
			||||||
        self.output = output
 | 
					        self.output = output
 | 
				
			||||||
        self.exclude = exclude
 | 
					        self.exclude = exclude
 | 
				
			||||||
        self.options = options
 | 
					        self.options = options
 | 
				
			||||||
        self.keep = keep
 | 
					        self.keep = keep
 | 
				
			||||||
        self.host = host
 | 
					        self.ssh_host = ssh_host
 | 
				
			||||||
        self.username = username
 | 
					        self.ssh_user = ssh_user
 | 
				
			||||||
        self.ssh_keyfile = ssh_keyfile
 | 
					        self.ssh_keyfile = ssh_keyfile
 | 
				
			||||||
        self.remote_sudo = remote_sudo
 | 
					        self.remote_sudo = remote_sudo
 | 
				
			||||||
        self._remove_before = remove_before
 | 
					        self._remove_before = remove_before
 | 
				
			||||||
@@ -179,7 +179,7 @@ class Backup:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            return 2
 | 
					            return 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.host is not None and self.username is not None:
 | 
					        if self.ssh_host is not None and self.ssh_user is not None:
 | 
				
			||||||
            self._remote = True
 | 
					            self._remote = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self._remote:
 | 
					        if self._remote:
 | 
				
			||||||
@@ -216,7 +216,7 @@ class Backup:
 | 
				
			|||||||
        self._output_dir = f'{self.output}/simple_backup/{now}'
 | 
					        self._output_dir = f'{self.output}/simple_backup/{now}'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self._remote:
 | 
					        if self._remote:
 | 
				
			||||||
            self._server = f'{self.username}@{self.host}:'
 | 
					            self._server = f'{self.ssh_user}@{self.ssh_host}:'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def remove_old_backups(self):
 | 
					    def remove_old_backups(self):
 | 
				
			||||||
        """Remove old backups if there are more than indicated by 'keep'"""
 | 
					        """Remove old backups if there are more than indicated by 'keep'"""
 | 
				
			||||||
@@ -330,11 +330,11 @@ class Backup:
 | 
				
			|||||||
        ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
 | 
					        ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            ssh.connect(self.host, username=self.username)
 | 
					            ssh.connect(self.ssh_host, username=self.ssh_user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return ssh
 | 
					            return ssh
 | 
				
			||||||
        except UserWarning:
 | 
					        except UserWarning:
 | 
				
			||||||
            k = input(f'Unknown key for host {self.host}. Continue anyway? (Y/N) ')
 | 
					            k = input(f'Unknown key for host {self.ssh_host}. Continue anyway? (Y/N) ')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if k[0].upper() == 'Y':
 | 
					            if k[0].upper() == 'Y':
 | 
				
			||||||
                ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
 | 
					                ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
 | 
				
			||||||
@@ -349,7 +349,7 @@ class Backup:
 | 
				
			|||||||
            pass
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            ssh.connect(self.host, username=self.username)
 | 
					            ssh.connect(self.ssh_host, username=self.ssh_user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return ssh
 | 
					            return ssh
 | 
				
			||||||
        except paramiko.SSHException:
 | 
					        except paramiko.SSHException:
 | 
				
			||||||
@@ -357,8 +357,8 @@ class Backup:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if self.ssh_keyfile is None:
 | 
					        if self.ssh_keyfile is None:
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                password = getpass(f'{self.username}@{self.host}\'s password: ')
 | 
					                password = getpass(f'{self.ssh_user}@{self.ssh_host}\'s password: ')
 | 
				
			||||||
                ssh.connect(self.host, username=self.username, password=password)
 | 
					                ssh.connect(self.ssh_host, username=self.ssh_user, password=password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                self._password_auth = True
 | 
					                self._password_auth = True
 | 
				
			||||||
                os.environ['SSHPASS'] = password
 | 
					                os.environ['SSHPASS'] = password
 | 
				
			||||||
@@ -410,7 +410,7 @@ class Backup:
 | 
				
			|||||||
                    pass
 | 
					                    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            ssh.connect(self.host, username=self.username, pkey=pkey)
 | 
					            ssh.connect(self.ssh_host, username=self.ssh_user, pkey=pkey)
 | 
				
			||||||
        except paramiko.SSHException:
 | 
					        except paramiko.SSHException:
 | 
				
			||||||
            logger.critical('SSH connection to server failed')
 | 
					            logger.critical('SSH connection to server failed')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -478,7 +478,7 @@ class Backup:
 | 
				
			|||||||
        if euid == 0 and self.ssh_keyfile is not None:
 | 
					        if euid == 0 and self.ssh_keyfile is not None:
 | 
				
			||||||
            rsync = f'{rsync} -e \'ssh -i {self.ssh_keyfile} -o StrictHostKeyChecking=no\''
 | 
					            rsync = f'{rsync} -e \'ssh -i {self.ssh_keyfile} -o StrictHostKeyChecking=no\''
 | 
				
			||||||
        elif self._password_auth and which('sshpass'):
 | 
					        elif self._password_auth and which('sshpass'):
 | 
				
			||||||
            rsync = f'{rsync} -e \'sshpass -e ssh -l {self.username} -o StrictHostKeyChecking=no\''
 | 
					            rsync = f'{rsync} -e \'sshpass -e ssh -l {self.ssh_user} -o StrictHostKeyChecking=no\''
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            rsync = f'{rsync} -e \'ssh -o StrictHostKeyChecking=no\''
 | 
					            rsync = f'{rsync} -e \'ssh -o StrictHostKeyChecking=no\''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -567,8 +567,8 @@ def _parse_arguments():
 | 
				
			|||||||
    parser.add_argument('-o', '--output', help='Output directory for the backup')
 | 
					    parser.add_argument('-o', '--output', help='Output directory for the backup')
 | 
				
			||||||
    parser.add_argument('-e', '--exclude', nargs='+', help='Files/directories/patterns to exclude from the backup')
 | 
					    parser.add_argument('-e', '--exclude', nargs='+', help='Files/directories/patterns to exclude from the backup')
 | 
				
			||||||
    parser.add_argument('-k', '--keep', type=int, help='Number of old backups to keep')
 | 
					    parser.add_argument('-k', '--keep', type=int, help='Number of old backups to keep')
 | 
				
			||||||
    parser.add_argument('--host', help='Server hostname (for remote backup)')
 | 
					    parser.add_argument('--ssh-host', help='Server hostname (for remote backup)')
 | 
				
			||||||
    parser.add_argument('-u', '--username', help='Username to connect to server (for remote backup)')
 | 
					    parser.add_argument('--ssh-user', help='Username to connect to server (for remote backup)')
 | 
				
			||||||
    parser.add_argument('--keyfile', help='SSH key location')
 | 
					    parser.add_argument('--keyfile', help='SSH key location')
 | 
				
			||||||
    parser.add_argument('-s', '--checksum', action='store_true',
 | 
					    parser.add_argument('-s', '--checksum', action='store_true',
 | 
				
			||||||
                        help='Use checksum rsync option to compare files')
 | 
					                        help='Use checksum rsync option to compare files')
 | 
				
			||||||
@@ -610,8 +610,8 @@ def _read_config(config_file):
 | 
				
			|||||||
                   'output': None,
 | 
					                   'output': None,
 | 
				
			||||||
                   'exclude': None,
 | 
					                   'exclude': None,
 | 
				
			||||||
                   'keep': -1,
 | 
					                   'keep': -1,
 | 
				
			||||||
                   'host': None,
 | 
					                   'ssh_host': None,
 | 
				
			||||||
                   'username': None,
 | 
					                   'ssh_user': None,
 | 
				
			||||||
                   'ssh_keyfile': None,
 | 
					                   'ssh_keyfile': None,
 | 
				
			||||||
                   'remote_sudo': False,
 | 
					                   'remote_sudo': False,
 | 
				
			||||||
                   'numeric_ids': False}
 | 
					                   'numeric_ids': False}
 | 
				
			||||||
@@ -660,14 +660,14 @@ def _read_config(config_file):
 | 
				
			|||||||
    config_args['keep'] = keep
 | 
					    config_args['keep'] = keep
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        host = config.get('server', 'host')
 | 
					        ssh_host = config.get('server', 'ssh_host')
 | 
				
			||||||
        username = config.get('server', 'username')
 | 
					        ssh_user = config.get('server', 'ssh_user')
 | 
				
			||||||
    except (configparser.NoSectionError, configparser.NoOptionError):
 | 
					    except (configparser.NoSectionError, configparser.NoOptionError):
 | 
				
			||||||
        host = None
 | 
					        ssh_host = None
 | 
				
			||||||
        username = None
 | 
					        ssh_user = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    config_args['host'] = host
 | 
					    config_args['ssh_host'] = ssh_host
 | 
				
			||||||
    config_args['username'] = username
 | 
					    config_args['ssh_user'] = ssh_user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        ssh_keyfile = config.get('server', 'ssh_keyfile')
 | 
					        ssh_keyfile = config.get('server', 'ssh_keyfile')
 | 
				
			||||||
@@ -732,8 +732,8 @@ def simple_backup():
 | 
				
			|||||||
    output = args.output if args.output is not None else config_args['output']
 | 
					    output = args.output if args.output is not None else config_args['output']
 | 
				
			||||||
    exclude = args.exclude if args.exclude is not None else config_args['exclude']
 | 
					    exclude = args.exclude if args.exclude is not None else config_args['exclude']
 | 
				
			||||||
    keep = args.keep if args.keep is not None else config_args['keep']
 | 
					    keep = args.keep if args.keep is not None else config_args['keep']
 | 
				
			||||||
    host = args.host if args.host is not None else config_args['host']
 | 
					    ssh_host = args.ssh_host if args.ssh_host is not None else config_args['ssh_host']
 | 
				
			||||||
    username = args.username if args.username is not None else config_args['username']
 | 
					    ssh_user = args.ssh_user if args.ssh_user is not None else config_args['ssh_user']
 | 
				
			||||||
    ssh_keyfile = args.keyfile if args.keyfile is not None else config_args['ssh_keyfile']
 | 
					    ssh_keyfile = args.keyfile if args.keyfile is not None else config_args['ssh_keyfile']
 | 
				
			||||||
    remote_sudo = args.remote_sudo if args.remote_sudo is not None else config_args['remote_sudo']
 | 
					    remote_sudo = args.remote_sudo if args.remote_sudo is not None else config_args['remote_sudo']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -756,7 +756,7 @@ def simple_backup():
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    rsync_options = ' '.join(rsync_options)
 | 
					    rsync_options = ' '.join(rsync_options)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    backup = Backup(inputs, output, exclude, keep, rsync_options, host, username, ssh_keyfile,
 | 
					    backup = Backup(inputs, output, exclude, keep, rsync_options, ssh_host, ssh_user, ssh_keyfile,
 | 
				
			||||||
                    remote_sudo, remove_before=args.remove_before_backup)
 | 
					                    remote_sudo, remove_before=args.remove_before_backup)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return_code = backup.check_params()
 | 
					    return_code = backup.check_params()
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user