diff --git a/PKGBUILD b/PKGBUILD new file mode 100644 index 0000000..8a21647 --- /dev/null +++ b/PKGBUILD @@ -0,0 +1,32 @@ +#Arch Linux PKGBUILD +# +#Maintainer: Daniele Fucini +# + +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" +} diff --git a/README.md b/README.md index 1a4febd..ff79de7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A simple backup script ## Description -simple-backup is just a bash script that allows you to backup your files. +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. diff --git a/config b/config index 58b67ed..b95cb5b 100644 --- a/config +++ b/config @@ -11,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 diff --git a/simple-backup b/simple-backup deleted file mode 100755 index 1e0b9b6..0000000 --- a/simple-backup +++ /dev/null @@ -1,489 +0,0 @@ -#!/bin/bash - -#Copyright 2017 Daniele Fucini - -#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 . - -#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.4.2" - 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 "-u, --user USER User performing the backup." - echo " Default: current user." - echo "-s, --checksum Use the checksum rsync option to compare files" - echo " (MUCH slower)." - echo "" - echo "If no option is given, the program uses the default" - echo "configuration file: $HOMEDIR/.simple_backup/config." - echo "" - echo "Report bugs to dfucini@gmail.com" - exit 0 -} - -#Read configuration file -function read_conf { - if [[ "$#" -eq 0 ]]; then - CONFIG="$HOMEDIR/.simple_backup/config" - - if [[ ! -f "$CONFIG" ]]; then - #If default config file doesn't exist, exit - echo "$(date): Backup failed (see errors.log)" >> $LOG - echo "Backup failed" - echo "Error: Configuration file not found" | tee $ERR - - #Fix ownership and permissions of log files if needed - if [[ ! -z $USER ]]; then - chown $USER:$USER $LOG && chmod 644 $LOG - chown $USER:$USER $ERR && chmod 644 $ERR - chown $USER:$USER $WARN && chmod 644 $WARN - fi - - mv $LOG "$HOMEDIR/.simple_backup/simple_backup.log" - mv $ERR "$HOMEDIR/.simple_backup/errors.log" - mv $WARN "$HOMEDIR/.simple_backup/warnings.log" - - exit 1 - fi - - else - CONFIG="$1" - - if [[ ! -f "$CONFIG" ]]; then - #If the provided configuration file doesn't exist, exit - echo "$(date): Backup failed (see errors.log)" >> $LOG - echo "Backup failed" - echo "Error: Configuration file not found" | tee -a $ERR - - #Fix ownership and permissions of log files if needed - if [[ ! -z $USER ]]; then - chown $USER:$USER $LOG && chmod 644 $LOG - chown $USER:$USER $ERR && chmod 644 $ERR - chown $USER:$USER $WARN && chmod 644 $WARN - fi - - mv $LOG "$HOMEDIR/.simple_backup/simple_backup.log" - mv $ERR "$HOMEDIR/.simple_backup/errors.log" - mv $WARN "$HOMEDIR/.simple_backup/warnings.log" - - exit 1 - fi - - fi - - #Create temporary files - INPUTS=$(mktemp) - EXCLUDE=$(mktemp) - BACKUP=$(mktemp) - NKEEP=$(mktemp) - - #Parse the configuration file - awk -v INPUTS="$INPUTS" -v EXCLUDE="$EXCLUDE" -v BACKUP="$BACKUP" \ - -v NKEEP="$NKEEP" -v UHOME="$HOMEDIR/" -F '[=,]' \ - '$1=="inputs" { for ( i=2; i<=NF; i++ ) { sub(/^~\//,UHOME,$i); print $i > INPUTS } } - $1=="backup_dir" { sub(/^~\//,UHOME,$2); print $2 > BACKUP } - $1=="exclude" { for ( i=2; i<=NF; i++ ) { sub(/^~\//,UHOME,$i); print $i > EXCLUDE } } - $1=="keep" { if ( $2 != NULL ) { print $2 > NKEEP } }' $CONFIG - - BACKUP_DEV=$(cat $BACKUP) - KEEP=$(cat $NKEEP) - - rm "$BACKUP" - rm "$NKEEP" - - if [[ -z "$BACKUP_DEV" || ! -d "$BACKUP_DEV" ]]; then - #If the backup directory is not set or doesn't exist, exit - echo "$(date): Backup failed (see errors.log)" >> $LOG - echo "Backup failed" - echo "Error: Output folder \"$BACKUP_DEV\" not found" | tee -a $ERR - - #Fix ownership and permissions of log files if needed - if [[ ! -z $USER ]]; then - chown $USER:$USER $LOG && chmod 644 $LOG - chown $USER:$USER $ERR && chmod 644 $ERR - chown $USER:$USER $WARN && chmod 644 $WARN - fi - - mv $LOG "$HOMEDIR/.simple_backup/simple_backup.log" - mv $ERR "$HOMEDIR/.simple_backup/errors.log" - mv $WARN "$HOMEDIR/.simple_backup/warnings.log" - - rm "$INPUTS" - rm "$EXCLUDE" - - 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 backups exist, 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" - - n_in=$(cat $INPUTS | wc -l) - - return -} - -#Parse options -function parse_options { - n_in=0 - - #Create a temporary file to store inputs - INPUTS=$(mktemp) - #Create temp file to store exclude patterns - EXCLUDE=$(mktemp) - - while [[ "$#" -gt 0 ]] - do - var="$1" - - case "$var" in - -h | --help) - help_function - - rm $INPUTS - rm $EXCLUDE - - exit 0 - ;; - - -i | --input) - - while [[ "$#" -gt 1 && ! "$2" =~ ^- ]] - do - input="$2" - - if [[ ! -e "$input" ]]; then - echo "Warning: input \"$input\" not found. Skipping" | tee -a $WARN - else - 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)" >> $LOG - echo "Backup failed" - echo "Error: output folder \"$BACKUP_DEV\" not found" | tee -a $ERR - - #Fix ownership and permissions of log files if needed - if [[ ! -z $USER ]]; then - chown $USER:$USER $LOG && chmod 644 $LOG - chown $USER:$USER $ERR && chmod 644 $ERR - chown $USER:$USER $WARN && chmod 644 $WARN - fi - - mv $LOG "$HOMEDIR/.simple_backup/simple_backup.log" - mv $ERR "$HOMEDIR/.simple_backup/errors.log" - mv $WARN "$HOMEDIR/.simple_backup/warnings.log" - - rm $INPUTS - rm $EXCLUDE - - 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) - rm "$EXCLUDE" - rm "$INPUTS" - - read_conf "$2" - return - ;; - - -u | --user) - if [[ ! -d "/home/$2" ]]; then - echo "$(date): Backup failed (see errors.log)" >> $LOG - echo "Backup failed" - echo "Error: user $2 doesn't exist" | tee -a $ERR - - if [[ ! -d "$HOMEDIR/.simple_backup" ]]; then - mkdir "$HOMEDIR/.simple_backup" - fi - - #Fix ownership and permissions of log files if needed - if [[ ! -z $USER ]]; then - chown $USER:$USER $LOG && chmod 644 $LOG - chown $USER:$USER $ERR && chmod 644 $ERR - chown $USER:$USER $WARN && chmod 644 $WARN - fi - - mv $LOG "$HOMEDIR/.simple_backup/simple_backup.log" - mv $ERR "$HOMEDIR/.simple_backup/errors.log" - mv $WARN "$HOMEDIR/.simple_backup/warnings.log" - - exit 1 - fi - - if [[ ! -d "/home/$2/.simple_backup" ]]; then - mkdir "/home/$2/.simple_backup" - - echo "Created directory \"$HOMEDIR/.simple_backup\"." - fi - - HOMEDIR="/home/$2" - USER="$2" - - shift - ;; - - -s | --checksum) - OPTIONS="-arcvh -H -X -R" - ;; - - *) - echo "$(date): Backup failed (see errors.log)" >> $LOG - echo "Backup failed" - echo "Error: Option $1 not recognised. Use 'simple-backup -h' to see available options" | tee -a $ERR - - #Fix ownership and permissions of log files if needed - if [[ ! -z $USER ]]; then - chown $USER:$USER $LOG && chmod 644 $LOG - chown $USER:$USER $ERR && chmod 644 $ERR - chown $USER:$USER $WARN && chmod 644 $WARN - fi - - mv $LOG "$HOMEDIR/.simple_backup/simple_backup.log" - mv $ERR "$HOMEDIR/.simple_backup/errors.log" - mv $WARN "$HOMEDIR/.simple_backup/warnings.log" - - exit 1 - ;; - esac - - shift - done - - return -} - -#Create temporary log files -LOG=$(mktemp) -ERR=$(mktemp) -WARN=$(mktemp) - -HOMEDIR="$HOME" -OPTIONS="-arvh -H -X -R" - -#Check number of parameters -if [[ "$#" -eq 0 ]]; then - if [[ ! -d "$HOMEDIR/.simple_backup" ]]; then - mkdir "$HOMEDIR/.simple_backup" - - echo "Created directory \"$HOMEDIR/.simple_backup\"." - echo "Copy there sample configuration and edit it" - echo "to your needs before running the backup," - echo "or pass options on the command line." - - exit 1 - fi - - read_conf -else - parse_options "$@" - - if [[ $n_in -gt 0 && ( -z $BACKUP_DIR || ! -d $BACKUP_DIR ) ]]; then - #If the backup directory is not set or doesn't exist, exit - echo "$(date): Backup failed (see errors.log)" >> $LOG - echo "Backup failed" - echo "Error: Output folder \"$BACKUP_DEV\" not found" | tee -a $ERR - - #Fix ownership and permissions of log files if needed - if [[ ! -z $USER ]]; then - chown $USER:$USER $LOG && chmod 644 $LOG - chown $USER:$USER $ERR && chmod 644 $ERR - chown $USER:$USER $WARN && chmod 644 $WARN - fi - - mv $LOG "$HOMEDIR/.simple_backup/simple_backup.log" - mv $ERR "$HOMEDIR/.simple_backup/errors.log" - mv $WARN "$HOMEDIR/.simple_backup/warnings.log" - - exit 1 - elif [[ $n_in -eq 0 && -z $BACKUP_DIR ]]; then - if [[ ! -d "$HOMEDIR/.simple_backup" ]]; then - mkdir "$HOMEDIR/.simple_backup" - - echo "Created directory \"$HOMEDIR/.simple_backup\"." - echo "Copy there sample configuration and edit it" - echo "to your needs before running the backup," - echo "or pass options on the command line." - - exit 1 - fi - - read_conf $HOMEDIR/.simple_backup/config - fi -fi - -if [[ $n_in -eq 0 ]]; then - echo "$(date): Backup finished (no files copied)" >> $LOG - echo "Backup finished" - echo "Warning: no valid input selected. Nothing to do" | tee -a $WARN - - #Fix ownership and permissions of log files if needed - if [[ ! -z $USER ]]; then - chown $USER:$USER $LOG && chmod 644 $LOG - chown $USER:$USER $ERR && chmod 644 $ERR - chown $USER:$USER $WARN && chmod 644 $WARN - fi - - mv $LOG "$HOMEDIR/.simple_backup/simple_backup.log" - mv $ERR "$HOMEDIR/.simple_backup/errors.log" - mv $WARN "$HOMEDIR/.simple_backup/warnings.log" - - exit 0 -fi - -echo "$(date): Starting backup" > $LOG -echo "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" >> $LOG - echo "Removing old backups..." - 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" >> $LOG - done<$REMOVE - - echo "Removed old backups" - - rm $REMOVE - fi - -fi - -# Sort input files for rsync efficiency -if [[ ! -z "$INPUTS" ]]; then - sort "$INPUTS" -o "$INPUTS" -fi - -echo "Copying files. This may take a long time..." - -if [[ -z "$LAST_BACKUP" ]]; then - rsync $OPTIONS --exclude-from="$EXCLUDE" --files-from="$INPUTS" / "$BACKUP_DIR" --ignore-missing-args >> $LOG 2>> $ERR -else - rsync $OPTIONS --link-dest="$LAST_BACKUP" --exclude-from="$EXCLUDE" --files-from="$INPUTS" / "$BACKUP_DIR" --ignore-missing-args >> $LOG 2>> $ERR -fi - -#Update the logs -if [[ $(cat $ERR | wc -l) -gt 0 ]]; then - echo "$(date): Backup finished with errors" >> $LOG - echo "Backup finished with errors" -elif [[ $(cat $WARN | wc -l) -gt 0 ]]; then - echo "$(date): Backup finished with warnings" >> $LOG - echo "Backup finished (warnings)" -else - echo "$(date): Backup finished" >> $LOG - echo "Backup finished" -fi - -#Fix ownership and permissions of log files if needed -if [[ ! -z $USER ]]; then - chown $USER:$USER $LOG && chmod 644 $LOG - chown $USER:$USER $ERR && chmod 644 $ERR - chown $USER:$USER $WARN && chmod 644 $WARN -fi - -#Copy log files in home directory -mv $LOG "$HOMEDIR/.simple_backup/simple_backup.log" -mv $ERR "$HOMEDIR/.simple_backup/errors.log" -mv $WARN "$HOMEDIR/.simple_backup/warnings.log" - -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" - -exit 0 diff --git a/simple-backup.py b/simple-backup.py new file mode 100755 index 0000000..d54bac9 --- /dev/null +++ b/simple-backup.py @@ -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()