Compare commits
No commits in common. "master" and "4.1.5" have entirely different histories.
40
PKGBUILD
40
PKGBUILD
@ -1,40 +0,0 @@
|
|||||||
# PKGBUILD
|
|
||||||
|
|
||||||
# Maintainer: Daniele Fucini <dfucini [at] gmail [dot] com>
|
|
||||||
|
|
||||||
pkgname=simple_backup
|
|
||||||
pkgdesc='Simple backup script that uses rsync to copy files'
|
|
||||||
pkgver=4.1.6
|
|
||||||
pkgrel=1
|
|
||||||
url="https://git.shouldnt.work/fuxino/${pkgname}"
|
|
||||||
arch=('any')
|
|
||||||
license=('GPL-3.0-or-later')
|
|
||||||
makedepends=('git'
|
|
||||||
'python-setuptools'
|
|
||||||
'python-build'
|
|
||||||
'python-installer'
|
|
||||||
'python-wheel')
|
|
||||||
depends=('python>=3.10'
|
|
||||||
'rsync'
|
|
||||||
'python-dotenv')
|
|
||||||
optdepends=('python-systemd: use systemd log'
|
|
||||||
'python-dbus: for desktop notifications'
|
|
||||||
'python-paramiko: for remote backup through ssh')
|
|
||||||
conflicts=('simple_backup-git')
|
|
||||||
source=(git+${url}?signed#tag=${pkgver})
|
|
||||||
validpgpkeys=('7E12BC1FF3B6EDB2CD8053EB981A2B2A3BBF5514')
|
|
||||||
sha256sums=('b3b29d9e2e1b7b949e95674d9a401e8eeb0d5f898e8450473dce94f799ee9df3')
|
|
||||||
|
|
||||||
build()
|
|
||||||
{
|
|
||||||
cd ${srcdir}/${pkgname}
|
|
||||||
python -m build --wheel --no-isolation
|
|
||||||
}
|
|
||||||
|
|
||||||
package()
|
|
||||||
{
|
|
||||||
cd ${srcdir}/${pkgname}
|
|
||||||
python -m installer --destdir=${pkgdir} dist/*.whl
|
|
||||||
install -Dm644 ${pkgname}/${pkgname}.conf ${pkgdir}/usr/share/doc/${pkgname}/${pkgname}.conf
|
|
||||||
install -Dm644 man/${pkgname}.1 ${pkgdir}/usr/share/man/man1/${pkgname}.1
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
.TH SIMPLE_BACKUP 1 2025-03-30 SIMPLE_BACKUP 4.1.6
|
.TH SIMPLE_BACKUP 1 2023-06-15 SIMPLE_BACKUP 3.2.6
|
||||||
.SH NAME
|
.SH NAME
|
||||||
simple_backup \- Backup files and folders using rsync
|
simple_backup \- Backup files and folders using rsync
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@ -187,6 +187,6 @@ Bad configuration file.
|
|||||||
.SH SEE ALSO
|
.SH SEE ALSO
|
||||||
.BR rsync (1)
|
.BR rsync (1)
|
||||||
.SH AUTHORS
|
.SH AUTHORS
|
||||||
.MT https://git.shouldnt.work/fuxino
|
.MT https://github.com/Fuxino
|
||||||
Daniele Fucini
|
Daniele Fucini
|
||||||
.ME
|
.ME
|
||||||
|
@ -6,7 +6,7 @@ long_description = file: README.md
|
|||||||
author = Daniele Fucini
|
author = Daniele Fucini
|
||||||
author_email = dfucini@gmail.com
|
author_email = dfucini@gmail.com
|
||||||
license = GPL3
|
license = GPL3
|
||||||
url = https://git.shouldnt.work/fuxino
|
url = https://github.com/Fuxino/simple_backup
|
||||||
classifiers =
|
classifiers =
|
||||||
Development Status :: 4 - Beta
|
Development Status :: 4 - Beta
|
||||||
Environment :: Console
|
Environment :: Console
|
||||||
@ -16,7 +16,6 @@ classifiers =
|
|||||||
Programming Language :: Python :: 3.10
|
Programming Language :: Python :: 3.10
|
||||||
Programming Language :: Python :: 3.11
|
Programming Language :: Python :: 3.11
|
||||||
Programming Language :: Python :: 3.12
|
Programming Language :: Python :: 3.12
|
||||||
Programming Language :: Python :: 3.13
|
|
||||||
Topic :: System :: Archiving :: Backup
|
Topic :: System :: Archiving :: Backup
|
||||||
|
|
||||||
[options]
|
[options]
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
"""Init."""
|
"""Init."""
|
||||||
|
|
||||||
__version__ = '4.1.6'
|
__version__ = '4.1.5'
|
||||||
|
@ -163,14 +163,12 @@ class Backup:
|
|||||||
"""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:
|
||||||
logger.info(
|
logger.info('No existing files or directories specified for backup. Nothing to do')
|
||||||
'No existing files or directories specified for backup. Nothing to do')
|
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
if self.output is None:
|
if self.output is None:
|
||||||
logger.critical(
|
logger.critical('No output path specified. Use -o argument or specify output path in configuration file')
|
||||||
'No output path specified. Use -o argument or specify output path in configuration file')
|
|
||||||
|
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
@ -183,8 +181,7 @@ class Backup:
|
|||||||
if self._ssh is None:
|
if self._ssh is None:
|
||||||
return 5
|
return 5
|
||||||
|
|
||||||
_, stdout, _ = self._ssh.exec_command(
|
_, stdout, _ = self._ssh.exec_command(f'if [ -d "{self.output}" ]; then echo "ok"; fi')
|
||||||
f'if [ -d "{self.output}" ]; then echo "ok"; fi')
|
|
||||||
|
|
||||||
output = stdout.read().decode('utf-8').strip()
|
output = stdout.read().decode('utf-8').strip()
|
||||||
|
|
||||||
@ -220,8 +217,7 @@ class Backup:
|
|||||||
if self._remote:
|
if self._remote:
|
||||||
assert self._ssh is not None
|
assert self._ssh is not None
|
||||||
|
|
||||||
_, stdout, _ = self._ssh.exec_command(
|
_, stdout, _ = self._ssh.exec_command(f'ls {self.output}/simple_backup')
|
||||||
f'ls {self.output}/simple_backup')
|
|
||||||
|
|
||||||
dirs = stdout.read().decode('utf-8').strip().split('\n')
|
dirs = stdout.read().decode('utf-8').strip().split('\n')
|
||||||
|
|
||||||
@ -238,17 +234,14 @@ class Backup:
|
|||||||
|
|
||||||
for i in range(n_backup - self.keep):
|
for i in range(n_backup - self.keep):
|
||||||
if self.remote_sudo:
|
if self.remote_sudo:
|
||||||
_, _, stderr = self._ssh.exec_command(
|
_, _, stderr = self._ssh.exec_command(f'sudo rm -r "{self.output}/simple_backup/{dirs[i]}"')
|
||||||
f'sudo rm -r "{self.output}/simple_backup/{dirs[i]}"')
|
|
||||||
else:
|
else:
|
||||||
_, _, stderr = self._ssh.exec_command(
|
_, _, stderr = self._ssh.exec_command(f'rm -r "{self.output}/simple_backup/{dirs[i]}"')
|
||||||
f'rm -r "{self.output}/simple_backup/{dirs[i]}"')
|
|
||||||
|
|
||||||
err = stderr.read().decode('utf-8').strip().split('\n')[0]
|
err = stderr.read().decode('utf-8').strip().split('\n')[0]
|
||||||
|
|
||||||
if err != '':
|
if err != '':
|
||||||
logger.error(
|
logger.error('Error while removing backup %s.', {dirs[i]})
|
||||||
'Error while removing backup %s.', {dirs[i]})
|
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
else:
|
else:
|
||||||
count += 1
|
count += 1
|
||||||
@ -274,11 +267,9 @@ class Backup:
|
|||||||
rmtree(f'{self.output}/simple_backup/{dirs[i]}')
|
rmtree(f'{self.output}/simple_backup/{dirs[i]}')
|
||||||
count += 1
|
count += 1
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
logger.error(
|
logger.error('Error while removing backup %s. Directory not found', dirs[i])
|
||||||
'Error while removing backup %s. Directory not found', dirs[i])
|
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
logger.error(
|
logger.error('Error while removing backup %s. Permission denied', dirs[i])
|
||||||
'Error while removing backup %s. Permission denied', dirs[i])
|
|
||||||
|
|
||||||
if count == 1:
|
if count == 1:
|
||||||
logger.info('Removed %d backup', count)
|
logger.info('Removed %d backup', count)
|
||||||
@ -293,8 +284,7 @@ class Backup:
|
|||||||
logger.critical('SSH connection to server failed')
|
logger.critical('SSH connection to server failed')
|
||||||
sys.exit(5)
|
sys.exit(5)
|
||||||
|
|
||||||
_, stdout, _ = self._ssh.exec_command(
|
_, stdout, _ = self._ssh.exec_command(f'find {self.output}/simple_backup/ -mindepth 1 -maxdepth 1 -type d | sort')
|
||||||
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')
|
||||||
|
|
||||||
if output[-1] != '':
|
if output[-1] != '':
|
||||||
@ -303,15 +293,13 @@ class Backup:
|
|||||||
logger.info('No previous backups available')
|
logger.info('No previous backups available')
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
dirs = sorted([f.path for f in os.scandir(
|
dirs = sorted([f.path for f in os.scandir(f'{self.output}/simple_backup') if f.is_dir(follow_symlinks=False)])
|
||||||
f'{self.output}/simple_backup') if f.is_dir(follow_symlinks=False)])
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
logger.info('No previous backups available')
|
logger.info('No previous backups available')
|
||||||
|
|
||||||
return
|
return
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
logger.critical(
|
logger.critical('Cannot access the backup directory. Permission denied')
|
||||||
'Cannot access the backup directory. Permission denied')
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_notify('Backup failed (check log for details)')
|
_notify('Backup failed (check log for details)')
|
||||||
@ -345,8 +333,7 @@ class Backup:
|
|||||||
|
|
||||||
return ssh
|
return ssh
|
||||||
except UserWarning:
|
except UserWarning:
|
||||||
k = input(
|
k = input(f'Unknown key for host {self.ssh_host}. Continue anyway? (Y/N) ')
|
||||||
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())
|
||||||
@ -369,10 +356,8 @@ class Backup:
|
|||||||
|
|
||||||
if self.ssh_keyfile is None:
|
if self.ssh_keyfile is None:
|
||||||
try:
|
try:
|
||||||
password = getpass(
|
password = getpass(f'{self.ssh_user}@{self.ssh_host}\'s password: ')
|
||||||
f'{self.ssh_user}@{self.ssh_host}\'s password: ')
|
ssh.connect(self.ssh_host, username=self.ssh_user, 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
|
||||||
@ -394,8 +379,7 @@ class Backup:
|
|||||||
try:
|
try:
|
||||||
pkey = RSAKey.from_private_key_file(self.ssh_keyfile)
|
pkey = RSAKey.from_private_key_file(self.ssh_keyfile)
|
||||||
except paramiko.PasswordRequiredException:
|
except paramiko.PasswordRequiredException:
|
||||||
password = getpass(
|
password = getpass(f'Enter passwphrase for key \'{self.ssh_keyfile}\': ')
|
||||||
f'Enter passwphrase for key \'{self.ssh_keyfile}\': ')
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pkey = RSAKey.from_private_key_file(self.ssh_keyfile, password)
|
pkey = RSAKey.from_private_key_file(self.ssh_keyfile, password)
|
||||||
@ -407,8 +391,7 @@ class Backup:
|
|||||||
pkey = Ed25519Key.from_private_key_file(self.ssh_keyfile)
|
pkey = Ed25519Key.from_private_key_file(self.ssh_keyfile)
|
||||||
except paramiko.PasswordRequiredException:
|
except paramiko.PasswordRequiredException:
|
||||||
try:
|
try:
|
||||||
pkey = Ed25519Key.from_private_key_file(
|
pkey = Ed25519Key.from_private_key_file(self.ssh_keyfile, password)
|
||||||
self.ssh_keyfile, password)
|
|
||||||
except paramiko.SSHException:
|
except paramiko.SSHException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -417,8 +400,7 @@ class Backup:
|
|||||||
pkey = ECDSAKey.from_private_key_file(self.ssh_keyfile)
|
pkey = ECDSAKey.from_private_key_file(self.ssh_keyfile)
|
||||||
except paramiko.PasswordRequiredException:
|
except paramiko.PasswordRequiredException:
|
||||||
try:
|
try:
|
||||||
pkey = ECDSAKey.from_private_key_file(
|
pkey = ECDSAKey.from_private_key_file(self.ssh_keyfile, password)
|
||||||
self.ssh_keyfile, password)
|
|
||||||
except paramiko.SSHException:
|
except paramiko.SSHException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -427,8 +409,7 @@ class Backup:
|
|||||||
pkey = DSSKey.from_private_key_file(self.ssh_keyfile)
|
pkey = DSSKey.from_private_key_file(self.ssh_keyfile)
|
||||||
except paramiko.PasswordRequiredException:
|
except paramiko.PasswordRequiredException:
|
||||||
try:
|
try:
|
||||||
pkey = DSSKey.from_private_key_file(
|
pkey = DSSKey.from_private_key_file(self.ssh_keyfile, password)
|
||||||
self.ssh_keyfile, password)
|
|
||||||
except paramiko.SSHException:
|
except paramiko.SSHException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -444,44 +425,31 @@ class Backup:
|
|||||||
def _returncode_log(self, returncode: int) -> None:
|
def _returncode_log(self, returncode: int) -> None:
|
||||||
match returncode:
|
match returncode:
|
||||||
case 2:
|
case 2:
|
||||||
logger.error(
|
logger.error('Rsync error (return code 2) - Protocol incompatibility')
|
||||||
'Rsync error (return code 2) - Protocol incompatibility')
|
|
||||||
case 3:
|
case 3:
|
||||||
logger.error(
|
logger.error('Rsync error (return code 3) - Errors selecting input/output files, dirs')
|
||||||
'Rsync error (return code 3) - Errors selecting input/output files, dirs')
|
|
||||||
case 4:
|
case 4:
|
||||||
logger.error(
|
logger.error('Rsync error (return code 4) - Requested action not supported')
|
||||||
'Rsync error (return code 4) - Requested action not supported')
|
|
||||||
case 5:
|
case 5:
|
||||||
logger.error(
|
logger.error('Rsync error (return code 5) - Error starting client-server protocol')
|
||||||
'Rsync error (return code 5) - Error starting client-server protocol')
|
|
||||||
case 10:
|
case 10:
|
||||||
logger.error(
|
logger.error('Rsync error (return code 10) - Error in socket I/O')
|
||||||
'Rsync error (return code 10) - Error in socket I/O')
|
|
||||||
case 11:
|
case 11:
|
||||||
logger.error(
|
logger.error('Rsync error (return code 11) - Error in file I/O')
|
||||||
'Rsync error (return code 11) - Error in file I/O')
|
|
||||||
case 12:
|
case 12:
|
||||||
logger.error(
|
logger.error('Rsync error (return code 12) - Error in rsync protocol data stream')
|
||||||
'Rsync error (return code 12) - Error in rsync protocol data stream')
|
|
||||||
case 22:
|
case 22:
|
||||||
logger.error(
|
logger.error('Rsync error (return code 22) - Error allocating core memory buffers')
|
||||||
'Rsync error (return code 22) - Error allocating core memory buffers')
|
|
||||||
case 23:
|
case 23:
|
||||||
logger.warning(
|
logger.warning('Rsync error (return code 23) - Partial transfer due to error')
|
||||||
'Rsync error (return code 23) - Partial transfer due to error')
|
|
||||||
case 24:
|
case 24:
|
||||||
logger.warning(
|
logger.warning('Rsync error (return code 24) - Partial transfer due to vanished source files')
|
||||||
'Rsync error (return code 24) - Partial transfer due to vanished source files')
|
|
||||||
case 30:
|
case 30:
|
||||||
logger.error(
|
logger.error('Rsync error (return code 30) - Timeout in data send/receive')
|
||||||
'Rsync error (return code 30) - Timeout in data send/receive')
|
|
||||||
case 35:
|
case 35:
|
||||||
logger.error(
|
logger.error('Rsync error (return code 35) - Timeout waiting for daemon connection')
|
||||||
'Rsync error (return code 35) - Timeout waiting for daemon connection')
|
|
||||||
case _:
|
case _:
|
||||||
logger.error(
|
logger.error('Rsync error (return code %d) - Check rsync(1) for details', returncode)
|
||||||
'Rsync error (return code %d) - Check rsync(1) for details', returncode)
|
|
||||||
|
|
||||||
# Function to read configuration file
|
# Function to read configuration file
|
||||||
@timing
|
@timing
|
||||||
@ -511,8 +479,7 @@ class Backup:
|
|||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
if count == 0:
|
if count == 0:
|
||||||
logger.info(
|
logger.info('No existing files or directories specified for backup. Nothing to do')
|
||||||
'No existing files or directories specified for backup. Nothing to do')
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_notify('Backup finished. No files copied')
|
_notify('Backup finished. No files copied')
|
||||||
@ -592,8 +559,7 @@ class Backup:
|
|||||||
if self._remote:
|
if self._remote:
|
||||||
assert self._ssh is not None
|
assert self._ssh is not None
|
||||||
|
|
||||||
_, stdout, _ = self._ssh.exec_command(
|
_, stdout, _ = self._ssh.exec_command(f'if [ -d "{self._output_dir}" ]; then echo "ok"; fi')
|
||||||
f'if [ -d "{self._output_dir}" ]; then echo "ok"; fi')
|
|
||||||
|
|
||||||
output = stdout.read().decode('utf-8').strip()
|
output = stdout.read().decode('utf-8').strip()
|
||||||
|
|
||||||
@ -616,12 +582,10 @@ class Backup:
|
|||||||
self._ssh.close()
|
self._ssh.close()
|
||||||
else:
|
else:
|
||||||
if returncode != 0:
|
if returncode != 0:
|
||||||
logger.error(
|
logger.error('Some errors occurred while performing the backup')
|
||||||
'Some errors occurred while performing the backup')
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_notify(
|
_notify('Some errors occurred while performing the backup. Check log for details')
|
||||||
'Some errors occurred while performing the backup. Check log for details')
|
|
||||||
except NameError:
|
except NameError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -637,7 +601,7 @@ class Backup:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def _parse_arguments() -> argparse.Namespace:
|
def _parse_arguments():
|
||||||
euid = os.geteuid()
|
euid = os.geteuid()
|
||||||
|
|
||||||
if euid == 0:
|
if euid == 0:
|
||||||
@ -652,39 +616,26 @@ def _parse_arguments() -> argparse.Namespace:
|
|||||||
epilog='See simple_backup(1) manpage for full documentation',
|
epilog='See simple_backup(1) manpage for full documentation',
|
||||||
formatter_class=MyFormatter)
|
formatter_class=MyFormatter)
|
||||||
|
|
||||||
parser.add_argument('-v', '--verbose', action='store_true',
|
parser.add_argument('-v', '--verbose', action='store_true', help='More verbose output')
|
||||||
help='More verbose output')
|
|
||||||
parser.add_argument('-c', '--config', default=f'{homedir}/.config/simple_backup/simple_backup.conf',
|
parser.add_argument('-c', '--config', default=f'{homedir}/.config/simple_backup/simple_backup.conf',
|
||||||
help='Specify location of configuration file')
|
help='Specify location of configuration file')
|
||||||
parser.add_argument('-i', '--inputs', nargs='+',
|
parser.add_argument('-i', '--inputs', nargs='+', help='Paths/files to backup')
|
||||||
help='Paths/files to backup')
|
parser.add_argument('-o', '--output', help='Output directory for the backup')
|
||||||
parser.add_argument(
|
parser.add_argument('-e', '--exclude', nargs='+', help='Files/directories/patterns to exclude from the backup')
|
||||||
'-o', '--output', help='Output directory for the backup')
|
parser.add_argument('-k', '--keep', type=int, help='Number of old backups to keep')
|
||||||
parser.add_argument('-e', '--exclude', nargs='+',
|
parser.add_argument('-u', '--user', help='Explicitly specify the user running the backup')
|
||||||
help='Files/directories/patterns to exclude from the backup')
|
parser.add_argument('-s', '--checksum', action='store_true', help='Use checksum rsync option to compare files')
|
||||||
parser.add_argument('-k', '--keep', type=int,
|
parser.add_argument('--ssh-host', help='Server hostname (for remote backup)')
|
||||||
help='Number of old backups to keep')
|
parser.add_argument('--ssh-user', help='Username to connect to server (for remote backup)')
|
||||||
parser.add_argument(
|
|
||||||
'-u', '--user', help='Explicitly specify the user running the 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('-z', '--compress', action='store_true',
|
parser.add_argument('-z', '--compress', action='store_true', help='Compress data during the transfer')
|
||||||
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')
|
||||||
parser.add_argument('--no-syslog', action='store_true',
|
parser.add_argument('--no-syslog', action='store_true', help='Disable systemd journal logging')
|
||||||
help='Disable systemd journal logging')
|
|
||||||
parser.add_argument('--rsync-options', nargs='+',
|
parser.add_argument('--rsync-options', nargs='+',
|
||||||
choices=['a', 'l', 'p', 't', 'g', 'o',
|
choices=['a', 'l', 'p', 't', 'g', 'o', 'c', 'h', 's', 'D', 'H', 'X'],
|
||||||
'c', 'h', 's', 'D', 'H', 'X'],
|
|
||||||
help='Specify options for rsync')
|
help='Specify options for rsync')
|
||||||
parser.add_argument('--remote-sudo', action='store_true',
|
parser.add_argument('--remote-sudo', action='store_true', help='Run rsync on remote server with sudo if allowed')
|
||||||
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)')
|
||||||
|
|
||||||
@ -693,7 +644,7 @@ def _parse_arguments() -> argparse.Namespace:
|
|||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
def _expand_inputs(inputs, user: Optional[str] = None):
|
def _expand_inputs(inputs, user=None):
|
||||||
expanded_inputs = []
|
expanded_inputs = []
|
||||||
|
|
||||||
for i in inputs:
|
for i in inputs:
|
||||||
@ -709,15 +660,14 @@ def _expand_inputs(inputs, user: Optional[str] = None):
|
|||||||
logger.warning('Cannot expand \'~\'. No user specified')
|
logger.warning('Cannot expand \'~\'. No user specified')
|
||||||
|
|
||||||
if len(i_ex) == 0:
|
if len(i_ex) == 0:
|
||||||
logger.warning(
|
logger.warning('No file or directory matching input %s. Skipping...', i)
|
||||||
'No file or directory matching input %s. Skipping...', i)
|
|
||||||
else:
|
else:
|
||||||
expanded_inputs.extend(i_ex)
|
expanded_inputs.extend(i_ex)
|
||||||
|
|
||||||
return expanded_inputs
|
return expanded_inputs
|
||||||
|
|
||||||
|
|
||||||
def _read_config(config_file, user: Optional[str] = None):
|
def _read_config(config_file, user=None):
|
||||||
config_args = {'inputs': None,
|
config_args = {'inputs': None,
|
||||||
'output': None,
|
'output': None,
|
||||||
'exclude': None,
|
'exclude': None,
|
||||||
@ -729,11 +679,7 @@ def _read_config(config_file, user: Optional[str] = None):
|
|||||||
'numeric_ids': False}
|
'numeric_ids': False}
|
||||||
|
|
||||||
if not os.path.isfile(config_file):
|
if not os.path.isfile(config_file):
|
||||||
if user is not None:
|
|
||||||
logger.warning('Config file %s does not exist', config_file)
|
logger.warning('Config file %s does not exist', config_file)
|
||||||
else:
|
|
||||||
logger.warning(
|
|
||||||
'User not specified. Can\'t read configuration file')
|
|
||||||
|
|
||||||
return config_args
|
return config_args
|
||||||
|
|
||||||
@ -813,7 +759,7 @@ def _read_config(config_file, user: Optional[str] = None):
|
|||||||
return config_args
|
return config_args
|
||||||
|
|
||||||
|
|
||||||
def _notify(text: str) -> None:
|
def _notify(text):
|
||||||
euid = os.geteuid()
|
euid = os.geteuid()
|
||||||
|
|
||||||
if euid == 0:
|
if euid == 0:
|
||||||
@ -827,15 +773,14 @@ def _notify(text: str) -> None:
|
|||||||
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'
|
||||||
|
|
||||||
obj = dbus.SessionBus().get_object('org.freedesktop.Notifications',
|
obj = dbus.SessionBus().get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications')
|
||||||
'/org/freedesktop/Notifications')
|
|
||||||
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() -> int:
|
def simple_backup():
|
||||||
"""Main"""
|
"""Main"""
|
||||||
|
|
||||||
args = _parse_arguments()
|
args = _parse_arguments()
|
||||||
@ -848,13 +793,7 @@ def simple_backup() -> int:
|
|||||||
|
|
||||||
if euid == 0:
|
if euid == 0:
|
||||||
user = os.getenv('SUDO_USER')
|
user = os.getenv('SUDO_USER')
|
||||||
|
|
||||||
if user is not None:
|
|
||||||
homedir = os.path.expanduser(f'~{user}')
|
homedir = os.path.expanduser(f'~{user}')
|
||||||
else:
|
|
||||||
logger.warning(
|
|
||||||
'Failed to detect user. You can use -u/--user parameter to manually specify it')
|
|
||||||
homedir = None
|
|
||||||
else:
|
else:
|
||||||
user = os.getenv('USER')
|
user = os.getenv('USER')
|
||||||
homedir = os.getenv('HOME')
|
homedir = os.getenv('HOME')
|
||||||
@ -885,8 +824,7 @@ def simple_backup() -> int:
|
|||||||
remote_sudo = args.remote_sudo or config_args['remote_sudo']
|
remote_sudo = args.remote_sudo or config_args['remote_sudo']
|
||||||
|
|
||||||
if args.rsync_options is None:
|
if args.rsync_options is None:
|
||||||
rsync_options = ['-a', '-r', '-v', '-h', '-H',
|
rsync_options = ['-a', '-r', '-v', '-h', '-H', '-X', '-s', '--ignore-missing-args', '--mkpath']
|
||||||
'-X', '-s', '--ignore-missing-args', '--mkpath']
|
|
||||||
else:
|
else:
|
||||||
rsync_options = ['-r', '-v']
|
rsync_options = ['-r', '-v']
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user