21 Commits
1.1 ... 2.0.0

Author SHA1 Message Date
2d50c7b158 Convert to Python 2019-09-16 18:12:19 +02:00
27324634b4 Fix version output 2017-06-24 23:42:01 +02:00
133a6b1a48 Fix minor bug
When using the '-s/--checksum' option, rsync output numbers were
not in human readable format. This commit fix this minor issue.
2017-06-24 23:29:39 +02:00
584532e2c7 Fix minor bug
Now the program is able to get as input arguments or output
directory files/directories containing spaces.
2017-06-23 13:47:44 +02:00
97d1a14260 Fix merge problem 2017-06-23 12:21:09 +02:00
71a2b6d01e Merge branch 'development' 2017-06-23 12:17:39 +02:00
6649925ec9 Add checksum option 2017-06-23 12:09:36 +02:00
871c8d6543 Now rsync outputs numbers in human-readable format 2017-06-20 19:26:22 +02:00
7035352cfd Fix minor bugs and options 2017-06-18 12:21:31 +02:00
d31080e5e4 Fix minor bugs 2017-06-17 15:11:55 +02:00
25954d5178 Fix notifications 2017-05-26 17:27:41 +02:00
87f3bca955 Merge branch 'development'
Fix notifications
2017-03-11 21:06:22 +01:00
a64219c321 Fix notifications 2017-03-11 21:04:05 +01:00
7c3eb9ed24 Update README.md 2015-12-08 11:32:45 +01:00
a2c4e54c47 Rename 2015-12-06 17:55:09 +01:00
b15af7f22f Change description 2015-12-06 17:27:09 +01:00
4623d023b9 Fix minor issue
Now ownership of the log files is set to the user specified by the
-u option if that option is used
2015-12-06 17:16:31 +01:00
898ef3bcd9 Fix user option bug 2015-12-03 19:50:27 +01:00
a708ec80ca Add user option
Added a command line option to specify the user performing the backup.
This is useful if running the program with sudo.
2015-11-26 01:03:33 +01:00
f5365fddff Add warning in example configuration 2015-11-25 13:26:57 +01:00
473ee59868 Improve configuration file parsing
The configuration file is now parsed with a simple awk program
which is simpler to understand and maintain
2015-11-25 12:33:49 +01:00
5 changed files with 633 additions and 555 deletions

32
PKGBUILD Normal file
View File

@ -0,0 +1,32 @@
#Arch Linux PKGBUILD
#
#Maintainer: Daniele Fucini <dfucini@gmail.com>
#
pkgname=simple-backup
pkgver=1.4.r0.g97d1a14
pkgrel=1
pkgdesc='Simple backup script that uses rsync to copy files'
arch=('any')
url="https://github.com/Fuxino"
license=('GPL3')
makedepends=('git')
depends=('python3'
'rsync')
install=${pkgname}.install
source=(git+https://github.com/Fuxino/${pkgname}.git
config)
sha256sums=('SKIP'
'd8b88cbb4e6d9cae68c1605496880065337484dff8ee4c06dce2578357e95574')
pkgver()
{
cd "$pkgname"
git describe --long --tags | sed 's/\([^-]*-g\)/r\1/;s/-/./g'
}
package()
{
install -Dm755 "${srcdir}/${pkgname}/${pkgname}" "${pkgdir}/usr/bin/${pkgname}"
install -Dm644 "${srcdir}/config" "${pkgdir}/etc/${pkgname}/config"
}

View File

@ -1,2 +1,10 @@
# simple_backup
A simple backup script using rsync
# simple-backup
A simple backup script
## Description
simple-backup is a Python script that allows you to backup your files.
It reads from a configuration file the files/directories that must be copied,
the destination directory for the backup and a few other options.
## Dependencies
rsync is used to perform the backup.

6
config
View File

@ -1,5 +1,7 @@
#Example config file for my_backup
# WARNING: Values should NOT be quoted, e.g. use inputs=/some/dir instead of inputs="/some/dir"
#Input directories. Use a comma to separate items
inputs=/home/my_home,/etc
@ -9,5 +11,5 @@ backup_dir=/media/Backup
#Exclude patterns. Use a comma to separate items
exclude=.gvfs,.cache*,[Cc]ache*,.thumbnails*,[Tt]rash*,*.backup*,*~,.dropbox*
#Number of snapshots to keep (default: keep all)
keep=
#Number of snapshots to keep (use -1 to keep all)
keep=-1

587
simple-backup.py Executable file
View File

@ -0,0 +1,587 @@
#!/usr/bin/python3
# Import libraries
from sys import exit, argv
import os
from os.path import expanduser, isfile, isdir, islink, exists
from shutil import move, rmtree
import subprocess
from datetime import datetime
from tempfile import mkstemp
class Backup():
def __init__(self, *args, **kwargs):
super(Backup, self).__init__(*args, **kwargs)
self.log_path = ''
self.logfile = None
self.err_path = ''
self.errfile = None
self.warn_path = ''
self.warnfile = None
self.homedir = ''
self.backup_dev = ''
self.backup_dir = ''
self.last_backup = ''
self.inputs = ''
self.inputs_path = ''
self.exclude_path = ''
self.exclude = ''
self.options = ''
self.user = ''
self.keep = -1
self.n_in = 0
# Help function
def help_function(self):
print('simple_backup, version 1.4.2')
print('')
print('Usage: {} [OPTIONS]'.format(argv[0]))
print('')
print('Options:')
print('-h, --help Print this help and exit.')
print('-c, --config CONFIG_FILE Use the specified configuration file')
print(' instead of the default one.')
print(' All other options are ignored.')
print('-i, --input INPUT [INPUT...] Specify a file/dir to include in the backup.')
print('-d, --directory DIR Specify the output directory for the backup.')
print('-e, --exclude PATTERN [PATTERN...] Specify a file/dir/pattern to exclude from')
print(' the backup.')
print('-k, --keep NUMBER Specify the number of old backups to keep.')
print(' Default: keep all.')
print('-u, --user USER User performing the backup.')
print(' Default: current user.')
print('-s, --checksum Use the checksum rsync option to compare files')
print(' (MUCH slower).')
print('')
print('If no option is given, the program uses the default')
print('configuration file: $HOMEDIR/.simple_backup/config.')
print('')
print('Report bugs to dfucini@gmail.com')
exit(0)
# Function to read configuration file
def read_conf(self, config=None):
if config is None:
config = self.homedir + '/.simple_backup/config'
if not isfile(config):
# If default config file doesn't exist, exit
log_message = str(datetime.now()) + ': Backup failed (see errors.log)'
self.logfile.write(log_message)
self.logfile.write('\n')
print('Backup failed')
err_message = 'Error: Configuration file not found'
print(err_message)
self.errfile.write(err_message)
self.errfile.write('\n')
# Fix ownership and permissions of log files if needed
if self.user != None:
pass
# chown $USER:$USER $LOG && chmod 644 $LOG
# chown $USER:$USER $ERR && chmod 644 $ERR
# chown $USER:$USER $WARN && chmod 644 $WARN
self.logfile.close()
self.errfile.close()
self.warnfile.close()
move(self.log_path, self.homedir + '/.simple_backup/simple_backup.log')
move(self.err_path, self.homedir + '/.simple_backup/errors.log')
move(self.warn_path, self.homedir + '/.simple_backup/warnings.log')
exit(1)
else:
if not isfile(config):
# If the provided configuration file doesn't exist, exit
log_message = str(datetime.now()) + ': Backup failed (see errors.log)'
self.logfile.write(log_message)
self.logfile.write('\n')
print('Backup failed')
err_message = 'Error: Configuration file not found'
print(err_message)
self.errfile.write(err_message)
self.errfile.write('\n')
# Fix ownership and permissions of log files if needed
if self.user != None:
pass
# chown $USER:$USER $LOG && chmod 644 $LOG
# chown $USER:$USER $ERR && chmod 644 $ERR
# chown $USER:$USER $WARN && chmod 644 $WARN
self.logfile.close()
self.errfile.close()
self.warnfile.close()
move(self.log_path, self.homedir + '/.simple_backup/simple_backup.log')
move(self.err_path, self.homedir + '/.simple_backup/errors.log')
move(self.warn_path, self.homedir + '/.simple_backup/warnings.log')
exit(1)
# Create temporary files
inputs_handle, self.inputs_path = mkstemp(prefix='tmp_inputs', text=True)
exclude_handle, self.exclude_path = mkstemp(prefix='tmp_exclude', text=True)
# Open temporary files
self.inputs = open(self.inputs_path, 'w')
self.exclude = open(self.exclude_path, 'w')
# Parse the configuration file
with open(config, 'r') as fp:
line = fp.readline()
while line:
if line[:7] == 'inputs=':
line = line[7:].rstrip()
input_values = line.split(',')
n_in = 0
for i in input_values:
if not exists(i):
warn_message = 'Warning: input "' + i + '" not found. Skipping'
print(warn_message)
self.warnfile.write(warn_message)
self.warnfile.write('\n')
else:
self.inputs.write(i)
self.inputs.write('\n')
self.n_in = self.n_in + 1
elif line[:11] == 'backup_dir=':
line = line[11:].rstrip()
self.backup_dev = line
elif line[:8] == 'exclude=':
line = line[8:].rstrip()
exclude_values = line.split(',')
for i in exclude_values:
self.exclude.write(i)
self.exclude.write('\n')
elif line[:5] == 'keep=':
line = line[5:].rstrip()
self.keep = int(line)
line = fp.readline()
fp.close()
self.inputs.close()
self.exclude.close()
# If the backup directory is not set or doesn't exist, exit
if self.backup_dev == '' or not isdir(self.backup_dev):
log_message = str(datetime.now()) + ': Backup failed (see errors.log)'
self.logfile.write(log_message)
self.logfile.write('\n')
print('Backup failed')
err_message = 'Error: Output folder "' + self.backup_dev + '" not found'
print(err_message)
self.errfile.write(err_message)
self.errfile.write('\n')
# Fix ownership and permissions of log files if needed
if self.user != None:
pass
# chown $USER:$USER $LOG && chmod 644 $LOG
# chown $USER:$USER $ERR && chmod 644 $ERR
# chown $USER:$USER $WARN && chmod 644 $WARN
self.logfile.close()
self.errfile.close()
self.warnfile.close()
move(self.log_path, self.homedir + '/.simple_backup/simple_backup.log')
move(self.err_path, self.homedir + '/.simple_backup/errors.log')
move(self.warn_path, self.homedir + '/.simple_backup/warnings.log')
exit(1)
self.backup_dir = self.backup_dev + '/simple_backup'
date = str(datetime.now())
# Create the backup subdirectory using date
if isdir(self.backup_dir):
# If previous backups exist, save link to the last backup
self.last_backup = self.backup_dir + '/last_backup'
if islink(self.last_backup):
self.last_backup = os.readlink(self.last_backup)
else:
self.last_backup = ''
self.backup_dir = self.backup_dir + '/' + date
os.makedirs(self.backup_dir)
return
# Function to parse options
def parse_options(self):
# Create temporary files
inputs_handle, self.inputs_path = mkstemp(prefix='tmp_inputs', text=True)
exclude_handle, self.exclude_path = mkstemp(prefix='tmp_exclude', text=True)
# Open temporary files
self.inputs = open(self.inputs_path, 'w')
self.exclude = open(self.exclude_path, 'w')
i = 1
while i < len(argv):
var = argv[i]
if var == '-h' or var == '--help':
self.help_function()
elif var == '-i' or var == '--input':
val = argv[i+1]
while i < len(argv) - 1 and val[0] != '-':
inp = val
if not exists(inp):
warn_message = 'Warning: input "' + inp + '" not found. Skipping'
print(warn_message)
self.warnfile.write(warn_message)
self.warnfile.write('\n')
else:
self.n_in = self.n_in + 1
self.inputs.write(inp)
self.inputs.write('\n')
i = i + 1
val = argv[i+1]
elif var == '-d' or var == '--directory':
self.backup_dev = argv[i+1]
if not exists(self.backup_dev) or not isdir(self.backup_dev):
log_message = str(datetime.now()) + ': Backup failed (see errors.log)'
self.logfile.write(log_message)
self.logfile.write('\n')
print('Backup failed')
err_message = 'Error: output folder "' + self.backup_dev + '" not found'
print(err_message)
self.errfile.write(err_message)
self.errfile.write('\n')
if self.user is not None:
pass
# chown $USER:$USER $LOG && chmod 644 $LOG
# chown $USER:$USER $ERR && chmod 644 $ERR
# chown $USER:$USER $WARN && chmod 644 $WARN
self.logfile.close()
self.errfile.close()
self.warnfile.close()
move(self.log_path, self.homedir + '/.simple_backup/simple_backup.log')
move(self.err_path, self.homedir + '/.simple_backup/errors.log')
move(self.warn_path, self.homedir + '/.simple_backup/warnings.log')
exit(1)
self.backup_dir = self.backup_dev + '/simple_backup'
date = str(datetime.now())
# Create the backup subdirectory using date
if isdir(self.backup_dir):
#If previous backups exist, save link to the last backup
self.last_backup = self.backup_dir + '/last_backup'
if islink(self.last_backup):
self.last_backup = os.readlink(self.last_backup)
else:
self.last_backup = ''
self.backup_dir = self.backup_dir + '/' + date
os.makedirs(self.backup_dir)
i = i + 1
elif var == '-e' or var == '--exclude':
val = argv[i+1]
while i < len(argv) - 1 and val[0] != '-':
exc = val
self.exclude.write(exc)
self.exclude.write('\n')
i = i + 1
val = argv[i+1]
elif var == '-k' or var == '--keep':
self.keep = int(argv[i+1])
i = i + 1
elif var == '-c' or var == '--config':
self.read_conf(argv[i+1])
i = i + 1
elif var == '-u' or var == '--user':
self.homedir = '/home/' + argv[i+1]
if not isdir(self.homedir):
log_message = str(datetime.now()) + ': Backup failed (see errors.log)'
self.logfile.write(log_message)
self.logfile.write('\n')
print('Backup failed')
err_message = 'Error: user "' + argv[i+1] + '"doesn\'t exist'
self.errfile.write(err_message)
self.errfile.write('\n')
if self.user is not None:
pass
# chown $USER:$USER $LOG && chmod 644 $LOG
# chown $USER:$USER $ERR && chmod 644 $ERR
# chown $USER:$USER $WARN && chmod 644 $WARN
self.logfile.close()
self.errfile.close()
self.warnfile.close()
move(self.log_path, self.homedir + '/.simple_backup/simple_backup.log')
move(self.err_path, self.homedir + '/.simple_backup/errors.log')
move(self.warn_path, self.homedir + '/.simple_backup/warnings.log')
exit(1)
if not isdir(self.homedir + '/.simple_backup'):
os.makedir(self.homedir + '/.simple_backup')
print('Create directory "' + self.homedir + '/.simple_backup".')
self.user = argv[i+1]
i = i + 1
elif var == '-s' or var == '--checksum':
self.options = '-arcvh -H -X'
else:
log_message = str(datetime.now()) + ': Backup failed (see errors.log)'
self.logfile.write(log_message)
self.logfile.write('\n')
print('Backup failed')
err_message = 'Error: Option "' + var + '" not recognised. Use "simple-backup -h" to see available options'
self.errfile.write(err_message)
self.errfile.write('\n')
# Fix ownership and permissions of log files if needed
if self.user is not None:
pass
# chown $USER:$USER $LOG && chmod 644 $LOG
# chown $USER:$USER $ERR && chmod 644 $ERR
# chown $USER:$USER $WARN && chmod 644 $WARN
self.logfile.close()
self.errfile.close()
self.warnfile.close()
move(self.log_path, self.homedir + '/.simple_backup/simple_backup.log')
move(self.err_path, self.homedir + '/.simple_backup/errors.log')
move(self.warn_path, self.homedir + '/.simple_backup/warnings.log')
exit(1)
i = i + 1
self.inputs.close()
self.exclude.close()
return
def exec_(self):
print('Copying files. This may take a long time...')
if self.last_backup == '':
rsync = 'rsync ' + self.options + ' --exclude-from=' + self.exclude_path +\
' --files-from=' + self.inputs_path + ' / "' + self.backup_dir +\
'" --ignore-missing-args >> ' + self.log_path + ' 2>> ' + self.err_path
else:
rsync = 'rsync ' + self.options + ' --link-dest="' + self.last_backup + '" --exclude-from=' +\
self.exclude_path + ' --files-from=' + self.inputs_path + ' / "' + self.backup_dir +\
'" --ignore-missing-args >> ' + self.log_path + ' 2>> ' + self.err_path
subprocess.run(rsync, shell=True)
return
def main():
backup = Backup()
# Create temporary log files
log_handle, backup.log_path = mkstemp(prefix='tmp_log', text=True)
err_handle, backup.err_path = mkstemp(prefix='tmp_err', text=True)
warn_handle, backup.warn_path = mkstemp(prefix='tmp_warn', text=True)
# Open log files
backup.logfile = open(backup.log_path, 'w')
backup.errfile = open(backup.err_path, 'w')
backup.warnfile = open(backup.warn_path, 'w')
# Set homedir and default options
backup.homedir = expanduser('~')
backup.options = '-arvh -H -X'
# Check number of parameters
if len(argv) == 1:
# If simple backup directory doesn't exist, create it and exit
if not isdir(backup.homedir + '/.simple_backup'):
os.makedir(backup.homedir + '/.simple_backup')
print('Created directory "' + backup.homedir + '/.simple_backup".')
print('Copy there the sample configuration and edit it')
print('to your needs before running the backup,')
print('or pass options on the command line.')
exit(1)
# Read configuration file
backup.read_conf()
else:
backup.parse_options()
if backup.n_in > 0 and (backup.backup_dir == '' or
not isdir(backup.backup_dir)):
#If the backup directory is not set or doesn't exist, exit
log_message = str(datetime.now()) + ': Backup failed (see errors.log)'
backup.logfile.write(log_message)
backup.logfile.write('\n')
err_message = 'Error: Output folder "' + backup.backup_dev.getBackupDev + '" not found'
backup.errfile.write(err_message)
backup.errfile.write('\n')
#Fix ownership and permissions of log files if needed
if backup.user is not None:
pass
# chown $USER:$USER $LOG && chmod 644 $LOG
# chown $USER:$USER $ERR && chmod 644 $ERR
# chown $USER:$USER $WARN && chmod 644 $WARN
backup.logfile.close()
backup.errfile.close()
backup.warnfile.close()
move(backup.log_path, backup.homedir + '/.simple_backup/simple_backup.log')
move(backup.err_path, backup.homedir + '/.simple_backup/errors.log')
move(backup.warn_path, backup.homedir + '/.simple_backup/warnings.log')
exit(1)
elif backup.n_in == 0 and backup.backup_dir == '':
if not isdir(backup.homedir + '/.simple_backup'):
os.makedir(backup.homedir + '/.simple_backup')
print('Created directory "' + backup.homedir + '/.simple_backup".')
print('Copy there the sample configuration and edit it')
print('to your needs before running the backup')
print('or pass options on the command line.')
exit(1)
backup.read_conf(backup.homedir + '/.simple_backup/config')
if backup.n_in == 0:
log_message = str(datetime.now()) + ': Backup finished (no files copied)'
backup.logfile.write(log_message)
backup.logfile.write('\n')
print('Backup finished (no files copied')
warn_message = 'Warning: no valid input selected. Nothing to do'
backup.warnfile.write(warn_message)
backup.warnfile.write('\n')
print(warn_message)
#Fix ownership and permissions of log files if needed
if backup.user is None:
pass
# chown $USER:$USER $LOG && chmod 644 $LOG
# chown $USER:$USER $ERR && chmod 644 $ERR
# chown $USER:$USER $WARN && chmod 644 $WARN
backup.logfile.close()
backup.errfile.close()
backup.warnfile.close()
move(backup.log_path, backup.homedir + '/.simple_backup/simple_backup.log')
move(backup.err_path, backup.homedir + '/.simple_backup/errors.log')
move(backup.warn_path, backup.homedir + '/.simple_backup/warnings.log')
exit(0)
log_message = str(datetime.now()) + ': Starting backup'
backup.logfile.write(log_message)
backup.logfile.write('\n')
print('Starting backup...')
#If specified, keep the last n backups and remove the others. Default: keep all
if backup.keep > -1:
dirs = os.listdir(backup.backup_dev + '/simple_backup')
if dirs.count('last_backup') > 0:
dirs.remove('last_backup')
n_backup = len(dirs) - 1
if n_backup > backup.keep:
log_message = str(datetime.now()) + ': Removing old backups'
backup.logfile.write(log_message)
backup.logfile.write('\n')
print('Removing old backups...')
dirs.sort()
err_flag = 0
for i in range(n_backup-backup.keep):
try:
rmtree(backup.backup_dev + '/simple_backup/' + dirs[i])
log_message = 'Removed backup: ' + dirs[i]
backup.logfile.write(log_message)
backup.logfile.write('\n')
except:
err_message = 'Error while removing backup ' + dirs[i]
backup.errfile.write(err_message)
backup.errfile.write('\n')
err_flag = 1
if err_flag:
print(err_message)
backup.logfile.close()
backup.errfile.close()
backup.warnfile.close()
backup.exec_()
# Update the logs
backup.logfile = open(backup.log_path, 'a')
if os.stat(backup.err_path).st_size > 0:
log_message = str(datetime.now()) + ': Backup finished with errors'
backup.logfile.write(log_message)
backup.logfile.write('\n')
print('Backup finished with errors')
elif os.stat(backup.warn_path).st_size > 0:
log_message = str(datetime.now()) + ': Backup finished with warnings'
backup.logfile.write(log_message)
backup.logfile.write('\n')
print('Backup finished (warnings)')
else:
log_message = str(datetime.now()) + ': Backup finished'
backup.logfile.write(log_message)
backup.logfile.write('\n')
print('Backup finished')
# Fix ownership and permissions of log files if needed
if backup.user is not None:
pass
# chown $USER:$USER $LOG && chmod 644 $LOG
# chown $USER:$USER $ERR && chmod 644 $ERR
# chown $USER:$USER $WARN && chmod 644 $WARN
backup.logfile.close()
# Copy log files in home directory
move(backup.log_path, backup.homedir + '/.simple_backup/simple_backup.log')
move(backup.err_path, backup.homedir + '/.simple_backup/errors.log')
move(backup.warn_path, backup.homedir + '/.simple_backup/warnings.log')
if islink(backup.backup_dev + '/simple_backup/last_backup'):
os.remove(backup.backup_dev + '/simple_backup/last_backup')
os.symlink(backup.backup_dir, backup.backup_dev + '/simple_backup/last_backup')
# Delete temporary files
os.remove(backup.inputs_path)
os.remove(backup.exclude_path)
exit(0)
if __name__ == '__main__':
main()

View File

@ -1,551 +0,0 @@
#!/bin/bash
#Copyright 2015 Daniele Fucini <dfucini@gmail.com>
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation, either version 3 of the License, or
#(at your option) any later version.
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#You should have received a copy of the GNU General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
#Version 1.1
#Simple backup script. Reads options, sources and destination from a configuration file or standard input
#Help function
function help_function {
echo "simple_backup, version 1.0.0"
echo ""
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo "-h, --help Print this help and exit."
echo "-c, --config CONFIG_FILE Use the specified configuration file"
echo " instead of the default one."
echo " All other options are ignored."
echo "-i, --input INPUT [INPUT...] Specify a file/dir to include in the backup."
echo "-d, --directory DIR Specify the output directory for the backup."
echo "-e, --exclude PATTERN [PATTERN...] Specify a file/dir/pattern to exclude from"
echo " the backup."
echo "-k, --keep NUMBER Specify the number of old backups to keep."
echo " Default: keep all."
echo ""
echo "If no option is given, the program uses the default"
echo "configuration file: $HOME/.simple_backup/config."
echo ""
echo "Report bugs to dfucini@gmail.com"
exit 0
}
#Read configuration file
function read_conf {
if [[ "$#" -eq 0 ]]; then
CONFIG="$HOME/.simple_backup/config"
else
CONFIG="$1"
if [[ ! -f "$CONFIG" ]]; then
#If the provided configuration file doesn't exist, exit
echo "$(date): Backup failed (see errors.log)" >> $HOME/.simple_backup/simple_backup.log
echo "Error: Configuration file not found" | tee -a $HOME/.simple_backup/errors.log
#If libnotify is installed, show desktop notification that backup failed
! command -v notify-send > /dev/null 2>&1 || DISPLAY=:0.0 notify-send -u low -t 10000 "Backup failed"
if [[ -f $HOME/.simple_backup/simple_backup.log.old ]]; then
rm -f $HOME/.simple_backup/simple_backup.log.old
fi
if [[ -f $HOME/.simple_backup/errors.log.old ]]; then
rm -f $HOME/.simple_backup/errors.log.old
fi
if [[ -f $HOME/.simple_backup/warnings.log.old ]]; then
rm -f $HOME/.simple_backup/warnings.log.old
fi
exit 1
fi
fi
while read line
do
#Ignore comments and empty lines
if [[ $line == \#* || $line == "" ]]; then
continue
else
#Get option name and values for the current line
var=$(echo "$line" | cut -d"=" -f1)
case "$var" in
#Files/folders to backup
inputs)
#Create a temporary file to store inputs
INPUTS=$(mktemp)
tmp=$(echo "$line" | cut -d"=" -f2)
n=$(echo "$tmp" | awk -F ',' '{print NF}') #Files/folders must be separated with commas
i=1
#j=1
n_in=0
#Read each input and save it to the INPUTS file
while [[ $i -le $n ]]
do
input=$(echo "$tmp" | cut -d"," -f$i)
input=$(echo "$input" | tr -d \"\')
if [[ "$input" =~ ^~/ ]]; then
input=$(echo ${input/\~/$HOME})
fi
if [[ ! -e "$input" ]]; then
#Warn the user if an input doesn't exist
echo "Warning: input \"$input\" not found. Skipping" | tee -a $HOME/.simple_backup/warning.log
else
n_in=$((n_in+1))
fi
echo "$input" >> $INPUTS
i=$((i+1))
done
;;
#Directory where the backup is saved
backup_dir)
BACKUP_DEV=$(echo "$line" | cut -d"=" -f2)
BACKUP_DEV=$(echo "$BACKUP_DEV" | tr -d \"\')
if [[ "$BACKUP_DEV" =~ ^~/ ]]; then
BACKUP_DEV=$(echo ${BACKUP_DEV/\~/$HOME})
fi
if [[ -z "$BACKUP_DEV" ]]; then
#If the backup directory is not set, exit
echo "$(date): Backup failed (see errors.log)" >> $HOME/.simple_backup/simple_backup.log
echo "Error: No backup folder set in configuration file" | tee -a $HOME/.simple_backup/errors.log
#If libnotify is installed, show desktop notification that backup failed
! command -v notify-send > /dev/null 2>&1 || DISPLAY=:0.0 notify-send -u low -t 10000 "Backup failed"
if [[ -f $HOME/.simple_backup/simple_backup.log.old ]]; then
rm -f $HOME/.simple_backup/simple_backup.log.old
fi
if [[ -f $HOME/.simple_backup/errors.log.old ]]; then
rm -f $HOME/.simple_backup/errors.log.old
fi
if [[ -f $HOME/.simple_backup/warnings.log.old ]]; then
rm -f $HOME/.simple_backup/warnings.log.old
fi
exit 1
fi
if [[ ! -d "$BACKUP_DEV" ]]; then
#If the backup directory doesn't exist, exit
echo "$(date): Backup failed (see errors.log)" >> $HOME/.simple_backup/simple_backup.log
echo "Error: Output folder \"$BACKUP_DEV\" not found" | tee -a $HOME/.simple_backup/errors.log
#If libnotify is installed, show desktop notification that backup failed
! command -v notify-send > /dev/null 2>&1 || DISPLAY=:0.0 notify-send -u low -t 10000 "Backup failed"
if [[ -f $HOME/.simple_backup/simple_backup.log.old ]]; then
rm -f $HOME/.simple_backup/simple_backup.log.old
fi
if [[ -f $HOME/.simple_backup/errors.log.old ]]; then
rm -f $HOME/.simple_backup/errors.log.old
fi
if [[ -f $HOME/.simple_backup/warnings.log.old ]]; then
rm -f $HOME/.simple_backup/warnings.log.old
fi
exit 1
fi
BACKUP_DIR=$BACKUP_DEV/simple_backup
DATE=$(date +%Y-%m-%d-%H:%M)
#Create the backup subdirectory using date
if [[ ! -d "$BACKUP_DIR" ]]; then
mkdir -p "$BACKUP_DIR/$DATE"
else
#If previous backup(s) exist(s), save link to the last backup
LAST_BACKUP=$(readlink -f "$BACKUP_DIR/last_backup")
mkdir "$BACKUP_DIR/$DATE"
fi
#Set the backup directory variable to the newly created subfolder
BACKUP_DIR="$BACKUP_DIR/$DATE"
;;
#Files/directories/patterns to exclude from backup
exclude)
#Create temp file to store exclude patterns
EXCLUDE=$(mktemp)
temp=$(echo "$line" | cut -d"=" -f2)
i=1
n=$(echo "$temp" | awk -F ',' '{print NF}') #Exclude patterns must be separated by commas
#Read each exclude pattern and save it in the temp file
while [[ $i -le $n ]]
do
var=$(echo "$temp" | cut -d"," -f$i)
var=$(echo "$var" | tr -d \"\')
if [[ "$var" =~ ^~/ ]]; then
var=$(echo ${var/\~/$HOME})
fi
echo "$var" >> $EXCLUDE
i=$((i+1))
done
;;
#Number of old backups to keep
keep)
KEEP=$(echo "$line" | cut -d"=" -f2)
;;
#Unrecognized options
*)
#Skip unrecognised options
echo "$(date): Warning: option \"$var\" not recognised. Skipping" >> $HOME/.simple_backup/warnings.log
;;
esac
fi
done<"$CONFIG"
return
}
#Parse options
function parse_options {
i=1
n_in=0
#Create temp file to store exclude patterns
EXCLUDE=$(mktemp)
while [[ "$#" -gt 0 ]]
do
var="$1"
case "$var" in
-h | --help)
help_function
if [[ -f $HOME/.simple_backup/simple_backup.log.old ]]; then
mv -f $HOME/.simple_backup/simple_backup.log.old $HOME/.simple_backup/simple_backup.log
fi
if [[ -f $HOME/.simple_backup/errors.log.old ]]; then
mv -f $HOME/.simple_backup/errors.log.old $HOME/.simple_backup/errors.log
fi
if [[ -f $HOME/.simple_backup/warnings.log.old ]]; then
mv -f $HOME/.simple_backup/warnings.log.old $HOME/.simple_backup/warnings.log
fi
exit 0
;;
-i | --input)
#Create a temporary file to store inputs
INPUTS=$(mktemp)
while [[ "$#" -gt 1 && ! "$2" =~ ^- ]]
do
input="$2"
if [[ -z "$input" ]]; then
echo "$(date): Backup failed (see errors.log)" >> $HOME/.simple_backup/simple_backup.log
echo "Error: bad options format" | tee -a $HOME/.simple_backup/errors.log
#If libnotify is installed, show desktop notification that backup failed
! command -v notify-send > /dev/null 2>&1 || DISPLAY=:0.0 notify-send -u low -t 10000 "Backup failed"
if [[ -f $HOME/.simple_backup/simple_backup.log.old ]]; then
rm -f $HOME/.simple_backup/simple_backup.log.old
fi
if [[ -f $HOME/.simple_backup/errors.log.old ]]; then
rm -f $HOME/.simple_backup/errors.log.old
fi
if [[ -f $HOME/.simple_backup/warnings.log.old ]]; then
rm -f $HOME/.simple_backup/warnings.log.old
fi
exit 1
fi
if [[ ! -e "$input" ]]; then
echo "Warning: input \"${INPUTS[$i]}\" not found. Skipping" | tee -a $HOME/.simple_backup/warnings.log
else
i=$((i+1))
n_in=$((n_in+1))
echo "$input" >> "$INPUTS"
fi
shift
done
;;
-d | --directory)
BACKUP_DEV="$2"
if [[ -z "$BACKUP_DEV" || ! -d "$BACKUP_DEV" ]]; then
echo "$(date): Backup failed (see errors.log)" >> $HOME/.simple_backup/simple_backup.log
echo "Error: output folder \"$BACKUP_DEV\" not found" | tee -a $HOME/.simple_backup/errors.log
#If libnotify is installed, show desktop notification that backup failed
! command -v notify-send > /dev/null 2>&1 || DISPLAY=:0.0 notify-send -u low -t 10000 "Backup failed"
if [[ -f $HOME/.simple_backup/simple_backup.log.old ]]; then
rm -f $HOME/.simple_backup/simple_backup.log.old
fi
if [[ -f $HOME/.simple_backup/errors.log.old ]]; then
rm -f $HOME/.simple_backup/errors.log.old
fi
if [[ -f $HOME/.simple_backup/warnings.log.old ]]; then
rm -f $HOME/.simple_backup/warnings.log.old
fi
exit 1
fi
BACKUP_DIR="$BACKUP_DEV/simple_backup"
DATE=$(date +%Y-%m-%d-%H:%M)
#Create the backup subdirectory using date
if [[ ! -d "$BACKUP_DIR" ]]; then
mkdir -p "$BACKUP_DIR/$DATE"
else
#If previous backup(s) exist(s), save link to the last backup
LAST_BACKUP=$(readlink -f "$BACKUP_DIR/last_backup")
mkdir "$BACKUP_DIR/$DATE"
fi
#Set the backup directory variable to the newly created subfolder
BACKUP_DIR="$BACKUP_DIR/$DATE"
shift
;;
-e | --exclude)
while [[ "$#" -gt 1 && ! "$2" =~ ^- ]]
do
echo "$2" >> "$EXCLUDE"
shift
done
;;
-k | --keep)
KEEP="$2"
shift
;;
-c | --config)
if [[ -f "$EXCLUDE" ]]; then
rm "$EXCLUDE"
fi
read_conf "$2"
return
;;
*)
echo "$(date): Backup failed (see errors.log)" >> $HOME/.simple_backup/simple_backup.log
echo "Error: Option $1 not recognised. Use 'simple-backup -h' to see available options" | tee -a $HOME/.simple_backup/errors.log
#If libnotify is installed, show desktop notification that backup failed
! command -v notify-send > /dev/null 2>&1 || DISPLAY=:0.0 notify-send -u low -t 10000 "Backup failed"
if [[ -f $HOME/.simple_backup/simple_backup.log.old ]]; then
rm -f $HOME/.simple_backup/simple_backup.log.old
fi
if [[ -f $HOME/.simple_backup/errors.log.old ]]; then
rm -f $HOME/.simple_backup/errors.log.old
fi
if [[ -f $HOME/.simple_backup/warnings.log.old ]]; then
rm -f $HOME/.simple_backup/warnings.log.old
fi
exit 1
;;
esac
shift
done
return
}
#Create HOME/.simple_backup if it doesn't exist
if [[ ! -d "$HOME/.simple_backup" ]]; then
mkdir "$HOME/.simple_backup"
fi
#Check number of parameters
if [[ "$#" -lt 1 ]]; then
#Read parameters from configuration file
default_config=1
else
default_config=0
fi
#Backup old log files
if [[ -f $HOME/.simple_backup/simple_backup.log ]]; then
mv -f $HOME/.simple_backup/simple_backup.log $HOME/.simple_backup/simple_backup.log.old
fi
if [[ -f $HOME/.simple_backup/errors.log ]]; then
mv -f $HOME/.simple_backup/errors.log $HOME/.simple_backup/errors.log.old
fi
if [[ -f $HOME/.simple_backup/warnings.log ]]; then
mv -f $HOME/.simple_backup/warnings.log $HOME/.simple_backup/warnings.log.old
fi
#If no input parameter is given, check existence of config file
if [[ $default_config -eq 1 && ! -f $HOME/.simple_backup/config ]]; then
#If no config file and input parameter is given, exit
echo "$(date): Backup failed (see errors.log)" >> $HOME/.simple_backup/simple_backup.log
echo "Error: Configuration file not found" | tee $HOME/.simple_backup/errors.log
#If libnotify is installed, show desktop notification that backup failed
! command -v notify-send > /dev/null 2>&1 || DISPLAY=:0.0 notify-send -u low -t 10000 "Backup failed"
if [[ -f $HOME/.simple_backup/simple_backup.log.old ]]; then
rm -f $HOME/.simple_backup/simple_backup.log.old
fi
if [[ -f $HOME/.simple_backup/errors.log.old ]]; then
rm -f $HOME/.simple_backup/errors.log.old
fi
if [[ -f $HOME/.simple_backup/warnings.log.old ]]; then
rm -f $HOME/.simple_backup/warnings.log.old
fi
exit 1
elif [[ $default_config -eq 1 && -f $HOME/.simple_backup/config ]]; then
#Read configuration file
read_conf
else
#Parse command line arguments
parse_options "$@"
fi
if [[ -z $n_in || $n_in -eq 0 ]]; then
echo "$(date): Backup finished (no files copied)" >> $HOME/.simple_backup/simple_backup.log
echo "Warning: no valid input selected. Nothing to do" | tee -a $HOME/.simple_backup/warnings.log
#If libnotify is installed, show desktop notification that backup finished
! command -v notify-send > /dev/null 2>&1 || DISPLAY=:0.0 notify-send -u low -t 10000 "Backup finished (warnings)"
if [[ -f $HOME/.simple_backup/simple_backup.log.old ]]; then
rm -f $HOME/.simple_backup/simple_backup.log.old
fi
if [[ -f $HOME/.simple_backup/errors.log.old ]]; then
rm -f $HOME/.simple_backup/errors.log.old
fi
if [[ -f $HOME/.simple_backup/warnings.log.old ]]; then
rm -f $HOME/.simple_backup/warnings.log.old
fi
exit 0
fi
echo "$(date): Starting backup" > $HOME/.simple_backup/simple_backup.log
#If libnotify is installed, show desktop notification that backup is starting
! command -v notify-send > /dev/null 2>&1 || DISPLAY=:0.0 notify-send -u low -t 10000 "Starting backup"
#If specified, keep the last n backups and remove the others. Default: keep all
if [[ -n $KEEP ]]; then
N_BACKUP=$(ls -l $BACKUP_DEV/simple_backup | grep -c ^d)
N_BACKUP=$(($N_BACKUP-1))
if [[ $N_BACKUP -gt $KEEP ]]; then
echo "$(date): Removing old backups..." >> $HOME/.simple_backup/simple_backup.log
REMOVE=$(mktemp)
find $BACKUP_DEV/simple_backup/* -maxdepth 0 -type d | sort | head -n $(($N_BACKUP - $KEEP)) >> $REMOVE
while read line
do
rm -r $line
echo "Removed backup: $line" >> $HOME/.simple_backup/simple_backup.log
done<$REMOVE
rm $REMOVE
fi
fi
# Sort input files for rsync efficiency
if [[ ! -z "$INPUTS" ]]; then
sort "$INPUTS" -o "$INPUTS"
fi
if [[ -z "$LAST_BACKUP" ]]; then
rsync -acrv -H -X -R --exclude-from="$EXCLUDE" --files-from="$INPUTS" / "$BACKUP_DIR" >> "$HOME/.simple_backup/simple_backup.log" 2>> "$HOME/.simple_backup/errors.log"
else
rsync -acrv -H -X -R --link-dest="$LAST_BACKUP" --exclude-from="$EXCLUDE" --files-from="$INPUTS" / "$BACKUP_DIR" >> "$HOME/.simple_backup/simple_backup.log" 2>> "$HOME/.simple_backup/errors.log"
fi
#Update the logs
if [[ -f $HOME/.simple_backup/errors.log && $(cat $HOME/.simple_backup/errors.log | wc -l) -gt 0 ]]; then
echo "$(date): Backup finished with errors" >> $HOME/.simple_backup/simple_backup.log
error_flag=1
elif [[ -f $HOME/.simple_backup/warnings.log && $(cat $HOME/.simple_backup/warnings.log | wc -l) -gt 0 ]]; then
echo "$(date): Backup finished with warnings" >> $HOME/.simple_backup/simple_backup.log
error_flag=2
else
echo "$(date): Backup finished" >> $HOME/.simple_backup/simple_backup.log
error_flag=0
fi
if [[ ! -z "$EXCLUDE" ]]; then
rm "$EXCLUDE"
fi
if [[ ! -z "$INPUTS" ]]; then
rm "$INPUTS"
fi
if [[ -L "$BACKUP_DEV/simple_backup/last_backup" ]]; then
rm "$BACKUP_DEV/simple_backup/last_backup"
fi
BACKUP_DIR_FULL=$(readlink -f "$BACKUP_DIR")
ln -sf "$BACKUP_DIR_FULL" "$BACKUP_DEV/simple_backup/last_backup"
if [[ $error_flag -eq 0 ]]; then
! command -v notify-send > /dev/null 2>&1 || DISPLAY=:0.0 notify-send -u low -t 10000 "Backup finished"
elif [[ $error_flag -eq 1 ]]; then
! command -v notify-send > /dev/null 2>&1 || DISPLAY=:0.0 notify-send -u low -t 10000 "Backup finished (errors)"
else
! command -v notify-send > /dev/null 2>&1 || DISPLAY=:0.0 notify-send -u low -t 10000 "Backup finished (warnings)"
fi
if [[ -f $HOME/.simple_backup/simple_backup.log.old ]]; then
rm -f $HOME/.simple_backup/simple_backup.log.old
fi
if [[ -f $HOME/.simple_backup/errors.log.old ]]; then
rm -f $HOME/.simple_backup/errors.log.old
fi
if [[ -f $HOME/.simple_backup/warnings.log.old ]]; then
rm -f $HOME/.simple_backup/warnings.log.old
fi
exit 0