Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
35b87c859e
|
|||
f3d5ebd276
|
|||
fb726a80ab
|
|||
4701ee0b05
|
|||
dd779d242b
|
|||
22a3e8d60f
|
@ -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
|
||||||
@ -39,7 +39,7 @@ Parameters specified on the command line will override those in the configuratio
|
|||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
.TP
|
.TP
|
||||||
.B \-h, \-\-help
|
.B \-h, \-\-help
|
||||||
Print a short help message and exit
|
Print a short help message and exit.
|
||||||
.TP
|
.TP
|
||||||
.B \-c, \-\-config FILE
|
.B \-c, \-\-config FILE
|
||||||
Specify the configuration file, useful to specify a different one from the default.
|
Specify the configuration file, useful to specify a different one from the default.
|
||||||
@ -52,21 +52,24 @@ or to use single or double quotes around them.
|
|||||||
.B \-o, \-\-output DIR
|
.B \-o, \-\-output DIR
|
||||||
Specify the directory where the files will be copied. The program will automatically
|
Specify the directory where the files will be copied. The program will automatically
|
||||||
create a subdirectory called \(aqsimple_backup\(aq (if it does not already exist) and
|
create a subdirectory called \(aqsimple_backup\(aq (if it does not already exist) and
|
||||||
inside this directory the actual backup directory (using the current date and time)
|
inside this directory the actual backup directory (using the current date and time).
|
||||||
.TP
|
.TP
|
||||||
.B \-e, \-\-exclude FILE|DIR|PATTERN [FILE|...]]
|
.B \-e, \-\-exclude FILE|DIR|PATTERN [FILE|...]]
|
||||||
Specify files, directories or patterns to exclude from the backup. Matching files and directories
|
Specify files, directories or patterns to exclude from the backup. Matching files and directories
|
||||||
will not be copied. Multiple elements can be specified, in the same way as for the \-\-input option
|
will not be copied. Multiple elements can be specified, in the same way as for the \-\-input option.
|
||||||
.TP
|
.TP
|
||||||
.B \-k, \-\-keep N
|
.B \-k, \-\-keep N
|
||||||
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 \-u, \-\-user USERNAME
|
||||||
Hostname of the server where to copy the files in case of remote backup through SSH
|
Explicitly specify the user running the backup (in case it is needed for home directory expansion).
|
||||||
.TP
|
.TP
|
||||||
.B \-u, \-\-username USERNAME
|
.B \-\-ssh\-host HOSTNAME
|
||||||
Username for connecting to the server in case of remote backup
|
Hostname of the server where to copy the files in case of remote backup through SSH.
|
||||||
|
.TP
|
||||||
|
.B \-\-ssh\-user USERNAME
|
||||||
|
Username for connecting to the server in case of remote backup.
|
||||||
.TP
|
.TP
|
||||||
.B \-\-keyfile FILE
|
.B \-\-keyfile FILE
|
||||||
Location of the SSH key for server authentication.
|
Location of the SSH key for server authentication.
|
||||||
@ -85,7 +88,7 @@ before performing the backup.
|
|||||||
Default behavior is to remove old backups after successfully completing the backup.
|
Default behavior is to remove old backups after successfully completing the backup.
|
||||||
.TP
|
.TP
|
||||||
.B \-\-no\-syslog
|
.B \-\-no\-syslog
|
||||||
Don't use systemd journal for logging
|
Don't use systemd journal for logging.
|
||||||
.TP
|
.TP
|
||||||
.B \-\-rsync\-options OPTIONS [OPTION...]
|
.B \-\-rsync\-options OPTIONS [OPTION...]
|
||||||
By default, the following rsync options are used:
|
By default, the following rsync options are used:
|
||||||
@ -104,7 +107,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
|
||||||
@ -116,14 +119,14 @@ Run rsync on the remote server with sudo. This is needed if you want to preserve
|
|||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B \-\-numeric\-ids
|
.B \-\-numeric\-ids
|
||||||
Use rsync \-\-numeric\-ids option. This causes rsync to use numeric uid/gid instead of trying to map uid/gid names from the local machine to the server
|
Use rsync \-\-numeric\-ids option. This causes rsync to use numeric uid/gid instead of trying to map uid/gid names from the local machine to the server.
|
||||||
.SH CONFIGURATION
|
.SH CONFIGURATION
|
||||||
An example configuration file is provided at \(aq/usr/share/doc/simple_backup/simple_backup.conf\(aq.
|
An example configuration file is provided at \(aq/usr/share/doc/simple_backup/simple_backup.conf\(aq.
|
||||||
Copy it to the default location ($HOME/.config/simple_backup) and edit it as needed.
|
Copy it to the default location ($HOME/.config/simple_backup) and edit it as needed.
|
||||||
.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 +148,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,24 +157,30 @@ 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
|
||||||
.B 0
|
.B 0
|
||||||
The backup was completed without errors
|
The backup was completed without errors.
|
||||||
.TP
|
.TP
|
||||||
.B 1
|
.B 1
|
||||||
No valid inputs selected for backup
|
No valid inputs selected for backup.
|
||||||
.TP
|
.TP
|
||||||
.B 2
|
.B 2
|
||||||
Backup failed because output directory for storing the backup does not exist
|
Backup failed because output directory for storing the backup does not exist.
|
||||||
.TP
|
.TP
|
||||||
.B 3
|
.B 3
|
||||||
Permission denied to access the output directory
|
Permission denied to access the output directory.
|
||||||
.TP
|
.TP
|
||||||
.B 4
|
.B 4
|
||||||
rsync error (rsync returned a non-zero value)
|
rsync error (rsync returned a non-zero value).
|
||||||
|
.TP
|
||||||
|
.B 5
|
||||||
|
SSH connection failed.
|
||||||
|
.TP
|
||||||
|
.B 6
|
||||||
|
Bad configuration file.
|
||||||
.SH SEE ALSO
|
.SH SEE ALSO
|
||||||
.BR rsync (1)
|
.BR rsync (1)
|
||||||
.SH AUTHORS
|
.SH AUTHORS
|
||||||
|
@ -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
|
||||||
@ -29,11 +30,15 @@ from getpass import getpass
|
|||||||
from glob import glob
|
from glob import glob
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
import paramiko
|
|
||||||
from paramiko import RSAKey, Ed25519Key, ECDSAKey, DSSKey
|
|
||||||
|
|
||||||
warnings.filterwarnings('error')
|
warnings.filterwarnings('error')
|
||||||
|
|
||||||
|
try:
|
||||||
|
import paramiko
|
||||||
|
from paramiko import RSAKey, Ed25519Key, ECDSAKey, DSSKey
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from systemd import journal
|
from systemd import journal
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@ -46,15 +51,6 @@ except ImportError:
|
|||||||
|
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
euid = os.geteuid()
|
|
||||||
|
|
||||||
if euid == 0:
|
|
||||||
user = os.getenv('SUDO_USER')
|
|
||||||
homedir = os.path.expanduser(f'~{user}')
|
|
||||||
else:
|
|
||||||
user = os.getenv('USER')
|
|
||||||
homedir = os.getenv('HOME')
|
|
||||||
|
|
||||||
logging.getLogger().setLevel(logging.DEBUG)
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
logger = logging.getLogger(os.path.basename(__file__))
|
logger = logging.getLogger(os.path.basename(__file__))
|
||||||
c_handler = StreamHandler()
|
c_handler = StreamHandler()
|
||||||
@ -114,9 +110,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
|
||||||
@ -138,15 +134,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
|
||||||
@ -161,7 +157,7 @@ class Backup:
|
|||||||
self._password_auth = False
|
self._password_auth = False
|
||||||
self._password = None
|
self._password = None
|
||||||
|
|
||||||
def check_params(self):
|
def check_params(self, homedir=''):
|
||||||
"""Check if parameters for the backup are valid"""
|
"""Check if parameters for the backup are valid"""
|
||||||
|
|
||||||
if self.inputs is None or len(self.inputs) == 0:
|
if self.inputs is None or len(self.inputs) == 0:
|
||||||
@ -174,14 +170,14 @@ 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:
|
||||||
self._ssh = self._ssh_connect()
|
self._ssh = self._ssh_connect(homedir)
|
||||||
|
|
||||||
if self._ssh is None:
|
if self._ssh is None:
|
||||||
sys.exit(1)
|
return 5
|
||||||
|
|
||||||
_, stdout, _ = self._ssh.exec_command(f'if [ -d "{self.output}" ]; then echo "ok"; fi')
|
_, stdout, _ = self._ssh.exec_command(f'if [ -d "{self.output}" ]; then echo "ok"; fi')
|
||||||
|
|
||||||
@ -211,7 +207,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'"""
|
||||||
@ -279,7 +275,7 @@ class Backup:
|
|||||||
if self._remote:
|
if self._remote:
|
||||||
if self._ssh is None:
|
if self._ssh is None:
|
||||||
logger.critical('SSH connection to server failed')
|
logger.critical('SSH connection to server failed')
|
||||||
sys.exit(1)
|
sys.exit(5)
|
||||||
|
|
||||||
_, stdout, _ = self._ssh.exec_command(f'find {self.output}/simple_backup/ -mindepth 1 -maxdepth 1 -type d | sort')
|
_, stdout, _ = self._ssh.exec_command(f'find {self.output}/simple_backup/ -mindepth 1 -maxdepth 1 -type d | sort')
|
||||||
output = stdout.read().decode('utf-8').strip().split('\n')
|
output = stdout.read().decode('utf-8').strip().split('\n')
|
||||||
@ -310,8 +306,12 @@ class Backup:
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
logger.info('No previous backups available')
|
logger.info('No previous backups available')
|
||||||
|
|
||||||
def _ssh_connect(self):
|
def _ssh_connect(self, homedir=''):
|
||||||
|
try:
|
||||||
ssh = paramiko.SSHClient()
|
ssh = paramiko.SSHClient()
|
||||||
|
except NameError:
|
||||||
|
logger.error('Install paramiko for ssh support')
|
||||||
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ssh.load_host_keys(filename=f'{homedir}/.ssh/known_hosts')
|
ssh.load_host_keys(filename=f'{homedir}/.ssh/known_hosts')
|
||||||
@ -321,11 +321,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())
|
||||||
@ -340,7 +340,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:
|
||||||
@ -348,8 +348,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
|
||||||
@ -401,7 +401,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')
|
||||||
|
|
||||||
@ -466,10 +466,12 @@ class Backup:
|
|||||||
rsync = f'/usr/bin/rsync {self.options} --link-dest="{self._last_backup}" --exclude-from=' +\
|
rsync = f'/usr/bin/rsync {self.options} --link-dest="{self._last_backup}" --exclude-from=' +\
|
||||||
f'{self._exclude_path} --files-from={self._inputs_path} / "{self._server}{self._output_dir}"'
|
f'{self._exclude_path} --files-from={self._inputs_path} / "{self._server}{self._output_dir}"'
|
||||||
|
|
||||||
|
euid = os.geteuid()
|
||||||
|
|
||||||
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\''
|
||||||
|
|
||||||
@ -547,6 +549,15 @@ class Backup:
|
|||||||
|
|
||||||
|
|
||||||
def _parse_arguments():
|
def _parse_arguments():
|
||||||
|
euid = os.geteuid()
|
||||||
|
|
||||||
|
if euid == 0:
|
||||||
|
user = os.getenv('SUDO_USER')
|
||||||
|
else:
|
||||||
|
user = os.getenv('USER')
|
||||||
|
|
||||||
|
homedir = os.path.expanduser(f'~{user}')
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(prog='simple_backup',
|
parser = argparse.ArgumentParser(prog='simple_backup',
|
||||||
description='Simple backup script written in Python that uses rsync to copy files',
|
description='Simple backup script written in Python that uses rsync to copy files',
|
||||||
epilog='See simple_backup(1) manpage for full documentation',
|
epilog='See simple_backup(1) manpage for full documentation',
|
||||||
@ -558,11 +569,11 @@ 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('-u', '--user', help='Explicitly specify the user running the backup')
|
||||||
parser.add_argument('-u', '--username', help='Username to connect to server (for remote backup)')
|
parser.add_argument('-s', '--checksum', action='store_true', help='Use checksum rsync option to compare files')
|
||||||
|
parser.add_argument('--ssh-host', help='Server hostname (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',
|
|
||||||
help='Use checksum rsync option to compare files')
|
|
||||||
parser.add_argument('-z', '--compress', action='store_true', help='Compress data during the transfer')
|
parser.add_argument('-z', '--compress', action='store_true', help='Compress data during the transfer')
|
||||||
parser.add_argument('--remove-before-backup', action='store_true',
|
parser.add_argument('--remove-before-backup', action='store_true',
|
||||||
help='Remove old backups before executing the backup, instead of after')
|
help='Remove old backups before executing the backup, instead of after')
|
||||||
@ -572,21 +583,27 @@ def _parse_arguments():
|
|||||||
help='Specify options for rsync')
|
help='Specify options for rsync')
|
||||||
parser.add_argument('--remote-sudo', action='store_true', help='Run rsync on remote server with sudo if allowed')
|
parser.add_argument('--remote-sudo', action='store_true', help='Run rsync on remote server with sudo if allowed')
|
||||||
parser.add_argument('--numeric-ids', action='store_true',
|
parser.add_argument('--numeric-ids', action='store_true',
|
||||||
help='Use rsync \'--numeric-ids\' option (don\'t map uid/gid values by name')
|
help='Use rsync \'--numeric-ids\' option (don\'t map uid/gid values by name)')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
def _expand_inputs(inputs):
|
def _expand_inputs(inputs, user=None):
|
||||||
expanded_inputs = []
|
expanded_inputs = []
|
||||||
|
|
||||||
for i in inputs:
|
for i in inputs:
|
||||||
if i == '':
|
if i == '':
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if user is not None:
|
||||||
i_ex = glob(os.path.expanduser(i.replace('~', f'~{user}')))
|
i_ex = glob(os.path.expanduser(i.replace('~', f'~{user}')))
|
||||||
|
else:
|
||||||
|
i_ex = glob(i)
|
||||||
|
|
||||||
|
if '~' in i:
|
||||||
|
logger.warning('Cannot expand \'~\'. No user specified')
|
||||||
|
|
||||||
if len(i_ex) == 0:
|
if len(i_ex) == 0:
|
||||||
logger.warning('No file or directory matching input %s. Skipping...', i)
|
logger.warning('No file or directory matching input %s. Skipping...', i)
|
||||||
@ -596,8 +613,16 @@ def _expand_inputs(inputs):
|
|||||||
return expanded_inputs
|
return expanded_inputs
|
||||||
|
|
||||||
|
|
||||||
def _read_config(config_file):
|
def _read_config(config_file, user=None):
|
||||||
config_args = {}
|
config_args = {'inputs': None,
|
||||||
|
'output': None,
|
||||||
|
'exclude': None,
|
||||||
|
'keep': -1,
|
||||||
|
'ssh_host': None,
|
||||||
|
'ssh_user': None,
|
||||||
|
'ssh_keyfile': None,
|
||||||
|
'remote_sudo': False,
|
||||||
|
'numeric_ids': False}
|
||||||
|
|
||||||
if not os.path.isfile(config_file):
|
if not os.path.isfile(config_file):
|
||||||
logger.warning('Config file %s does not exist', config_file)
|
logger.warning('Config file %s does not exist', config_file)
|
||||||
@ -617,13 +642,17 @@ def _read_config(config_file):
|
|||||||
inputs = config.get(section, 'inputs')
|
inputs = config.get(section, 'inputs')
|
||||||
|
|
||||||
inputs = inputs.split(',')
|
inputs = inputs.split(',')
|
||||||
inputs = _expand_inputs(inputs)
|
inputs = _expand_inputs(inputs, user)
|
||||||
inputs = list(set(inputs))
|
inputs = list(set(inputs))
|
||||||
|
|
||||||
config_args['inputs'] = inputs
|
config_args['inputs'] = inputs
|
||||||
|
|
||||||
output = config.get(section, 'backup_dir')
|
output = config.get(section, 'backup_dir')
|
||||||
|
|
||||||
|
if user is not None:
|
||||||
output = os.path.expanduser(output.replace('~', f'~{user}'))
|
output = os.path.expanduser(output.replace('~', f'~{user}'))
|
||||||
|
elif user is None and '~' in output:
|
||||||
|
logger.warning('Cannot expand \'~\', no user specified')
|
||||||
|
|
||||||
config_args['output'] = output
|
config_args['output'] = output
|
||||||
|
|
||||||
@ -643,14 +672,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')
|
||||||
@ -677,12 +706,15 @@ def _read_config(config_file):
|
|||||||
|
|
||||||
|
|
||||||
def _notify(text):
|
def _notify(text):
|
||||||
_euid = os.geteuid()
|
euid = os.geteuid()
|
||||||
|
|
||||||
if _euid == 0:
|
if euid == 0:
|
||||||
uid = os.getenv('SUDO_UID')
|
uid = os.getenv('SUDO_UID')
|
||||||
else:
|
else:
|
||||||
uid = os.geteuid()
|
uid = euid
|
||||||
|
|
||||||
|
if uid is None:
|
||||||
|
return
|
||||||
|
|
||||||
os.seteuid(int(uid))
|
os.seteuid(int(uid))
|
||||||
os.environ['DBUS_SESSION_BUS_ADDRESS'] = f'unix:path=/run/user/{uid}/bus'
|
os.environ['DBUS_SESSION_BUS_ADDRESS'] = f'unix:path=/run/user/{uid}/bus'
|
||||||
@ -691,7 +723,7 @@ def _notify(text):
|
|||||||
obj = dbus.Interface(obj, 'org.freedesktop.Notifications')
|
obj = dbus.Interface(obj, 'org.freedesktop.Notifications')
|
||||||
obj.Notify('', 0, '', 'simple_backup', text, [], {'urgency': 1}, 10000)
|
obj.Notify('', 0, '', 'simple_backup', text, [], {'urgency': 1}, 10000)
|
||||||
|
|
||||||
os.seteuid(int(_euid))
|
os.seteuid(int(euid))
|
||||||
|
|
||||||
|
|
||||||
def simple_backup():
|
def simple_backup():
|
||||||
@ -699,6 +731,22 @@ def simple_backup():
|
|||||||
|
|
||||||
args = _parse_arguments()
|
args = _parse_arguments()
|
||||||
|
|
||||||
|
if args.user:
|
||||||
|
user = args.user
|
||||||
|
homedir = os.path.expanduser(f'~{user}')
|
||||||
|
else:
|
||||||
|
euid = os.geteuid()
|
||||||
|
|
||||||
|
if euid == 0:
|
||||||
|
user = os.getenv('SUDO_USER')
|
||||||
|
homedir = os.path.expanduser(f'~{user}')
|
||||||
|
else:
|
||||||
|
user = os.getenv('USER')
|
||||||
|
homedir = os.getenv('HOME')
|
||||||
|
|
||||||
|
if homedir is None:
|
||||||
|
homedir = ''
|
||||||
|
|
||||||
if args.no_syslog:
|
if args.no_syslog:
|
||||||
try:
|
try:
|
||||||
logger.removeHandler(j_handler)
|
logger.removeHandler(j_handler)
|
||||||
@ -706,17 +754,17 @@ def simple_backup():
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
config_args = _read_config(args.config)
|
config_args = _read_config(args.config, user)
|
||||||
except (configparser.NoSectionError, configparser.NoOptionError):
|
except (configparser.NoSectionError, configparser.NoOptionError):
|
||||||
logger.critical('Bad configuration file')
|
logger.critical('Bad configuration file')
|
||||||
sys.exit(1)
|
return 6
|
||||||
|
|
||||||
inputs = args.inputs if args.inputs is not None else config_args['inputs']
|
inputs = args.inputs if args.inputs is not None else config_args['inputs']
|
||||||
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']
|
||||||
|
|
||||||
@ -739,10 +787,10 @@ 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(homedir)
|
||||||
|
|
||||||
if return_code == 0:
|
if return_code == 0:
|
||||||
return backup.run()
|
return backup.run()
|
||||||
|
Reference in New Issue
Block a user