diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..2bc9774 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,52 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, and in the interest of +fostering an open and welcoming community, we pledge to respect all people who +contribute through reporting issues, posting feature requests, updating +documentation, submitting pull requests or patches, and other activities. + +We are committed to making participation in this project a harassment-free +experience for everyone, regardless of level of experience, gender, gender +identity and expression, sexual orientation, disability, personal appearance, +body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, such as physical or electronic + addresses, without explicit permission +* Other unethical or unprofessional conduct + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +By adopting this Code of Conduct, project maintainers commit themselves to +fairly and consistently applying these principles to every aspect of managing +this project. Project maintainers who do not follow or enforce the Code of +Conduct may be permanently removed from the project team. + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project maintainers at msfdev@metasploit.com. If +the incident involves a committer, you may report directly to +egypt@metasploit.com or todb@metasploit.com. + +All complaints will be reviewed and investigated and will result in a +response that is deemed necessary and appropriate to the circumstances. +Maintainers are obligated to maintain confidentiality with regard to the +reporter of an incident. + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.3.0, available at +[http://contributor-covenant.org/version/1/3/0/][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/3/0/ \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 index b18f3a2..4e322cf --- a/README.md +++ b/README.md @@ -32,8 +32,8 @@ This list consists of only a few examples; applications can be endless: - Configuration for silently backup - All backup are organized into a catalog - List single or all backup by the catalog -- Backup single PC, with Full,Incremental and Mirror mode; -- Backup more PCs, with Full,Incremental and Mirror mode (with parallelism algorithm); +- Backup single PC, with Full,Incremental,Differential and Mirror mode; +- Backup more PCs, with Full,Incremental,Differential and Mirror mode (with parallelism algorithm); - Backup custom folder or predefined data (User,Config,Application,System,Log) - Restore backup on the same PC - Restore backup in other PC @@ -64,7 +64,7 @@ bb --help Backup a single PC or server is a everyday task. But most of the data may not change in the various backups made; then, in these cases, an incremental backup is needed. -Butterfly Backup natively supports incremental backups, starting from a full. +Butterfly Backup natively supports incremental and differential backups, starting from a full. In this case, the first backup to be performed on the machine will be as follows: ```bash bb backup --computer pc1 --destination /nas/mybackup --data User Config --type MacOS --mode Full @@ -148,7 +148,7 @@ bb --help ## One more thing The name butterfly, is born precisely because agent-less; like a butterfly takes the pollen from a flower and brings it elsewhere. -A backup or restore is performed without any iterationresponsibility on the part of the final machine. +A backup or restore is performed without any iteration responsibility on the part of the final machine. The performances are not altered. While all the operations of Butterfly Backup are carried out, the impacted machine can continuously work with peace of mind. diff --git a/_config.yml b/_config.yml old mode 100644 new mode 100755 diff --git a/bb.man b/bb.man new file mode 100644 index 0000000..49ae9f0 --- /dev/null +++ b/bb.man @@ -0,0 +1,179 @@ +.\" Manpage for Butterfly Backup. +.\" Contact matteo.guadrini@hotmail.it to correct errors or typos. +.TH man 1 "11 Dec 2018" "1.4.0" "bb man page" +.SH NAME +bb \- Butterfly Backup - backup/restore/archive tool , agentless +.SH SYNOPSIS +bb [ACTION] [OPTIONS] + +bb [-h] [--verbose] [--log] [--dry-run] [--version] + {config,backup,restore,archive,list,export} ... +.SH DESCRIPTION +Butterfly Backup is a simple command line wrapper of rsync for complex task, written in python. +.SH OPTIONS +.TP +action: + Valid action + + {config,backup,restore,archive,list,export} + Available actions + config Configuration options + backup Backup options + restore Restore options + archive Archive options + list List options + export Export options + +.B backup +[ACTION] + + --computer HOSTNAME, -c HOSTNAME + Hostname or ip address to backup + --list LIST, -L LIST File list of computers or ip addresses to backup + --destination DESTINATION, -d DESTINATION + Destination path + --mode {Full,Incremental,Differential,Mirror}, -m {Full,Incremental,Differential,Mirror} + Backup mode + --data {User,Config,Application,System,Log} [{User,Config,Application,System,Log} ...], -D {User,Config,Application,System,Log} [{User,Config,Application,System,Log} ...] + Data of which you want to backup + --custom-data, -D CUSTOMDATA [CUSTOMDATA ...], -C CUSTOMDATA [CUSTOMDATA ...] + Custom path of which you want to backup + --user USER, -u USER Login name used to log into the remote host (being + backed up) + --type {Unix,Windows,MacOS}, -t {Unix,Windows,MacOS} + Type of operating system to backup + --compress, -z Compress data + --retention RETENTION, -r RETENTION + Number of days of backup retention + --parallel PARALLEL, -p PARALLEL + Number of parallel jobs + --timeout TIMEOUT, -T TIMEOUT + I/O timeout in seconds + --skip-error, -e Skip error. + +.B list +[ACTION] + + --catalog CATALOG, -C CATALOG + Folder where is catalog file + --backup-id ID, -i ID + Backup-id of backup + --archived, -a List only archived backup + --cleaned, -c List only cleaned backup + --computer HOSTNAME, -H HOSTNAME + List only match hostname or ip + --oneline, -o One line output + +.B restore +[ACTION] + + --catalog CATALOG, -C CATALOG + Folder where is catalog file + --backup-id ID, -i ID + Backup-id of backup + --last, -L Last available backup + --user USER, -u USER Login name used to log into the remote host (where + you're restoring) + --computer HOSTNAME, -c HOSTNAME + Hostname or ip address to perform restore + --type {Unix,Windows,MacOS}, -t {Unix,Windows,MacOS} + Type of operating system to perform restore + --timeout TIMEOUT, -T TIMEOUT + I/O timeout in seconds + --mirror, -m Mirror mode + --skip-error, -e Skip error. + +.B config +[ACTION] + +Init configuration: + --new, -n Generate new configuration + --remove, -r Remove exist configuration + +Deploy configuration: + --deploy DEPLOY_HOST, -d DEPLOY_HOST + Deploy configuration to client: hostname or ip address + --user DEPLOY_USER, -u DEPLOY_USER + User of the remote machine + +.B archive +[ACTION] + + --catalog CATALOG, -C CATALOG + Folder where is catalog file + --days DAYS, -D DAYS Number of days of archive retention + --destination DESTINATION, -d DESTINATION + Archive destination path + +.B export +[ACTION] + --catalog CATALOG, -C CATALOG + Folder where is catalog file + --backup-id ID, -i ID + Backup-id of backup + --destination DESTINATION, -d DESTINATION + Destination path + --mirror, -m Mirror mode + --cut, -c Cut mode. Delete source + --include INCLUDE [INCLUDE ...], -I INCLUDE [INCLUDE ...] + Include pattern + --exclude EXCLUDE [EXCLUDE ...], -E EXCLUDE [EXCLUDE ...] + Exclude pattern + --timeout TIMEOUT, -T TIMEOUT + I/O timeout in seconds + --skip-error, -e Skip error + +.B optional arguments +[OPTIONS] + + -h, --help show help message and exit + --verbose, -v Enable verbosity + --log, -l Create a log + --dry-run, -N Dry run mode + --version, -V Print version + +.SH EXAMPLES +Show full help: + O_O>$ bb --help + +Backup single machine: + O_O>$ bb backup --computer host1 --destination /mnt/backup --data User Config --type MacOS + +Backup multiple machine: + O_O>$ > hosts.txt + host1 + host2 + host3 + ^D + O_O>$ bb backup --list hosts.txt --destination /mnt/backup --data User Config --type MacOS + +List catalog backups: + O_O>$ bb list --catalog /mnt/backup + +List backup details: + O_O>$ bb list --catalog /mnt/backup --backup-id dd6de2f2-9a1e-11e8-82b0-005056a664e0 + +Restore machine with log: + O_O>$ bb restore --catalog /mnt/backup --backup-id dd6de2f2-9a1e-11e8-82b0-005056a664e0 --computer host1 --log + +Archive backups older than 3 days: + O_O>$ bb archive --catalog /mnt/backup/ --days 3 --destination /mnt/archive/ --verbose + +Create configuration (RSA key): + O_O>$ bb config --new + +Deploy configuration to machine: + O_O>$ bb config --deploy host1 + +Export a backup to another path: + O_O>$ bb export --catalog /mnt/backup/ --backup-id f0f700e8-0435-11e9-9e78-005056a664e0 --destination /mnt/export + +.SH SEE ALSO +Full documentation is here: +https://butterfly-backup.readthedocs.io/en/latest/ +.SH BUGS +No known bugs. +.SH AUTHOR +Matteo Guadrini +.SH COPYRIGHT +(c) Matteo Guadrini. All rights reserved. \ No newline at end of file diff --git a/bb.py b/bb.py old mode 100644 new mode 100755 index 7d7f90e..fbe3383 --- a/bb.py +++ b/bb.py @@ -31,7 +31,7 @@ from utility import print_verbose # region Global Variables -VERSION = '1.2.0' +VERSION = '1.4.0' # endregion @@ -127,7 +127,7 @@ def run_in_parallel(fn, commands, limit): # Run the function proc = pool.apply_async(func=fn, args=(command, )) jobs.append(proc) - print('Start {0} on {1}'.format(args.action, plog['hostname'])) + print('Start {0} {1}'.format(args.action, plog['hostname'])) print_verbose(args.verbose, "rsync command: {0}".format(command)) utility.write_log(log_args['status'], plog['destination'], 'INFO', 'Start process {0} on {1}'.format( args.action, plog['hostname'] @@ -249,6 +249,7 @@ def compose_command(flags, host): print_verbose(args.verbose, 'Build a rsync command') # Set rsync binary command = ['rsync'] + catalog = read_catalog(catalog_path) if flags.action == 'backup': # Set mode option if flags.mode == 'Full': @@ -257,11 +258,11 @@ def compose_command(flags, host): # Write catalog file write_catalog(catalog_path, backup_id, 'type', 'Full') elif flags.mode == 'Incremental': - last_full = get_last_full(catalog_path) - if last_full: + last_bck = get_last_backup(catalog) + if last_bck: command.append('-ahu') command.append('--no-links') - command.append('--link-dest={0}'.format(last_full)) + command.append('--link-dest={0}'.format(last_bck[0])) # Write catalog file write_catalog(catalog_path, backup_id, 'type', 'Incremental') else: @@ -269,6 +270,19 @@ def compose_command(flags, host): command.append('--no-links') # Write catalog file write_catalog(catalog_path, backup_id, 'type', 'Full') + elif flags.mode == 'Differential': + last_full = get_last_full(catalog) + if last_full: + command.append('-ahu') + command.append('--no-links') + command.append('--link-dest={0}'.format(last_full[0])) + # Write catalog file + write_catalog(catalog_path, backup_id, 'type', 'Differential') + else: + command.append('-ah') + command.append('--no-links') + # Write catalog file + write_catalog(catalog_path, backup_id, 'type', 'Full') elif flags.mode == 'Mirror': command.append('-ah') command.append('--delete') @@ -277,6 +291,9 @@ def compose_command(flags, host): # Set verbosity if flags.verbose: command.append('-vP') + # Set quite mode + if flags.skip_err: + command.append('--quiet') # Set compress mode if flags.compress: command.append('-z') @@ -298,6 +315,9 @@ def compose_command(flags, host): command.append('-ahu --no-perms --no-owner --no-group') if flags.verbose: command.append('-vP') + # Set quite mode + if flags.skip_err: + command.append('--quiet') # Set I/O timeout if flags.timeout: command.append('--timeout={0}'.format(flags.timeout)) @@ -316,6 +336,31 @@ def compose_command(flags, host): ) utility.write_log(log_args['status'], log_args['destination'], 'INFO', 'rsync log path: {0}'.format(log_path)) + elif flags.action == 'export': + command.append('-ahu --no-perms --no-owner --no-group') + if flags.verbose: + command.append('-vP') + # Set quite mode + if flags.skip_err: + command.append('--quiet') + # Set I/O timeout + if flags.timeout: + command.append('--timeout={0}'.format(flags.timeout)) + # Set mirror mode + if flags.mirror: + command.append('--delete') + command.append('--ignore-times') + # Set dry-run mode + if flags.dry_run: + command.append('--dry-run') + utility.write_log(log_args['status'], log_args['destination'], 'INFO', 'dry-run mode activate') + if flags.log: + log_path = os.path.join(rpath, 'export.log') + command.append( + '--log-file={0}'.format(log_path) + ) + utility.write_log(log_args['status'], log_args['destination'], 'INFO', + 'rsync log path: {0}'.format(log_path)) print_verbose(args.verbose, 'Command flags are: {0}'.format(' '.join(command))) return command @@ -402,7 +447,11 @@ def compose_destination(computer_name, folder): """ # Create root folder of backup first_layer = os.path.join(folder, computer_name) - second_layer = os.path.join(first_layer, utility.time_for_folder()) + # Check if backup is a Mirror or not + if args.mode != 'Mirror': + second_layer = os.path.join(first_layer, utility.time_for_folder()) + else: + second_layer = os.path.join(first_layer, 'mirror_backup') if not os.path.exists(first_layer): os.mkdir(first_layer) utility.write_log(log_args['status'], log_args['destination'], 'INFO', @@ -421,14 +470,21 @@ def get_last_full(catalog): """ Get the last full :param catalog: configparser object - :return: path (string) + :return: path (string), os (string) """ - config = read_catalog(catalog) + config = catalog if config: dates = [] for bid in config.sections(): - if config.get(bid, 'type') == 'Full' and config.get(bid, 'name') == hostname: - dates.append(utility.string_to_time(config.get(bid, 'timestamp'))) + if config.get(bid, 'type') == 'Full' \ + and config.get(bid, 'name') == hostname \ + and (not config.has_option(bid, 'cleaned') or not config.has_option(bid, 'archived')): + try: + dates.append(utility.string_to_time(config.get(bid, 'timestamp'))) + except configparser.NoOptionError: + print(utility.PrintColor.RED + + "ERROR: Corrupted catalog! No found timestamp in {0}".format(bid) + utility.PrintColor.END) + exit(2) if dates: last_full = utility.time_to_string(max(dates)) if last_full: @@ -437,7 +493,7 @@ def get_last_full(catalog): if config.get(bid, 'type') == 'Full' and \ config.get(bid, 'name') == hostname and \ config.get(bid, 'timestamp') == last_full: - return config.get(bid, 'path') + return config.get(bid, 'path'), config.get(bid, 'os') else: return False @@ -446,14 +502,20 @@ def get_last_backup(catalog): """ Get the last available backup :param catalog: configparser object - :return: path (string) + :return: path (string), os (string) """ config = catalog dates = [] if config: for bid in config.sections(): - if config.get(bid, 'name') == hostname: - dates.append(utility.string_to_time(config.get(bid, 'timestamp'))) + if config.get(bid, 'name') == hostname \ + and (not config.has_option(bid, 'cleaned') or not config.has_option(bid, 'archived')): + try: + dates.append(utility.string_to_time(config.get(bid, 'timestamp'))) + except configparser.NoOptionError: + print(utility.PrintColor.RED + + "ERROR: Corrupted catalog! No found timestamp in {0}".format(bid) + utility.PrintColor.END) + exit(2) if dates: dates.sort() last = utility.time_to_string(dates[-1]) @@ -461,6 +523,8 @@ def get_last_backup(catalog): for bid in config.sections(): if config.get(bid, 'name') == hostname and config.get(bid, 'timestamp') == last: return config.get(bid, 'path'), config.get(bid, 'os') + else: + return False def count_full(config, name): @@ -607,8 +671,8 @@ def deploy_configuration(computer, user): print_verbose(args.verbose, 'Public id_rsa is {0}'.format(id_rsa_pub_file)) if not dry_run('Copying configuration to {0}'.format(computer)): if os.path.exists(id_rsa_pub_file): - print('Copying configuration to' + utility.PrintColor.BOLD + ' {0}'.format(computer) + utility.PrintColor.END + - '; write the password:') + print('Copying configuration to' + utility.PrintColor.BOLD + ' {0}'.format(computer) + + utility.PrintColor.END + '; write the password:') return_code = subprocess.call('ssh-copy-id -i {0} {1}@{2}'.format(id_rsa_pub_file, user, computer), shell=True) print_verbose(args.verbose, 'Return code of ssh-copy-id: {0}'.format(return_code)) @@ -620,7 +684,8 @@ def deploy_configuration(computer, user): computer) + utility.PrintColor.END) else: - print(utility.PrintColor.YELLOW + "WARNING: Public key ~/.ssh/id_rsa.pub is not exist" + utility.PrintColor.END) + print(utility.PrintColor.YELLOW + "WARNING: Public key ~/.ssh/id_rsa.pub is not exist" + + utility.PrintColor.END) exit(2) @@ -640,7 +705,8 @@ def remove_configuration(): os.remove(id_rsa_file) else: print( - utility.PrintColor.YELLOW + "WARNING: Private key ~/.ssh/id_rsa is not exist" + utility.PrintColor.END) + utility.PrintColor.YELLOW + "WARNING: Private key ~/.ssh/id_rsa is not exist" + + utility.PrintColor.END) exit(2) # Remove public key file id_rsa_pub_file = os.path.join(ssh_folder, 'id_rsa.pub') @@ -696,7 +762,8 @@ def new_configuration(): id_rsa.write(private_key_str) else: print(utility.PrintColor.YELLOW + "WARNING: Private key ~/.ssh/id_rsa exists" + utility.PrintColor.END) - print('If you want to use the existing key, run "bb config --deploy name_of_machine", otherwise to remove it, ' + print('If you want to use the existing key, run "bb config --deploy name_of_machine", ' + 'otherwise to remove it, ' 'run "bb config --remove"') exit(2) # Create private key file @@ -708,7 +775,8 @@ def new_configuration(): id_rsa_pub.write(public_key_str) else: print(utility.PrintColor.YELLOW + "WARNING: Public key ~/.ssh/id_rsa.pub exists" + utility.PrintColor.END) - print('If you want to use the existing key, run "bb config --deploy name_of_machine", otherwise to remove it, ' + print('If you want to use the existing key, run "bb config --deploy name_of_machine", ' + 'otherwise to remove it, ' 'run "bb config --remove"') exit(2) print(utility.PrintColor.GREEN + "SUCCESS: New configuration successfully created!" + utility.PrintColor.END) @@ -724,13 +792,9 @@ def check_configuration(ip): try: out = check_output(["ssh-keyscan", "{0}".format(ip)]) if not out: - print(utility.PrintColor.RED + - '''ERROR: For bulk or silently backup to deploy configuration! -See bb deploy --help or specify --verbose - ''' + utility.PrintColor.END) + return False except subprocess.CalledProcessError: - print(utility.PrintColor.RED + 'ERROR: Keyscan failed! Check ip {0}'.format(ip) + utility.PrintColor.END) - exit(1) + return False def parse_arguments(): @@ -777,7 +841,7 @@ def parse_arguments(): group_backup.add_argument('--destination', '-d', help='Destination path', dest='destination', action='store', required=True) group_backup.add_argument('--mode', '-m', help='Backup mode', dest='mode', action='store', - choices=['Full', 'Incremental', 'Mirror'], default='Incremental') + choices=['Full', 'Incremental', 'Differential', 'Mirror'], default='Incremental') data_or_custom = group_backup.add_mutually_exclusive_group(required=True) data_or_custom.add_argument('--data', '-D', help='Data of which you want to backup', dest='data', action='store', choices=['User', 'Config', 'Application', 'System', 'Log'], nargs='+') @@ -795,6 +859,7 @@ def parse_arguments(): type=int, default=5) group_backup.add_argument('--timeout', '-T', help='I/O timeout in seconds', dest='timeout', action='store', type=int) + group_backup.add_argument('--skip-error', '-e', help='Skip error', dest='skip_err', action='store_true') # restore session restore = action.add_parser('restore', help='Restore options', parents=[parent_parser]) group_restore = restore.add_argument_group(title='Restore options') @@ -813,6 +878,7 @@ def parse_arguments(): group_restore.add_argument('--timeout', '-T', help='I/O timeout in seconds', dest='timeout', action='store', type=int) group_restore.add_argument('--mirror', '-m', help='Mirror mode', dest='mirror', action='store_true') + group_restore.add_argument('--skip-error', '-e', help='Skip error', dest='skip_err', action='store_true') # archive session archive = action.add_parser('archive', help='Archive options', parents=[parent_parser]) group_archive = archive.add_argument_group(title='Archive options') @@ -827,9 +893,33 @@ def parse_arguments(): group_list = list_action.add_argument_group(title='List options') group_list.add_argument('--catalog', '-C', help='Folder where is catalog file', dest='catalog', action='store', required=True) - group_list.add_argument('--backup-id', '-i', help='Backup-id of backup', dest='id', action='store') - group_list.add_argument('--archived', '-a', help='List only archived backup', dest='archived', action='store_true') - group_list.add_argument('--cleaned', '-c', help='List only cleaned backup', dest='cleaned', action='store_true') + group_list_mutually = group_list.add_mutually_exclusive_group() + group_list_mutually.add_argument('--backup-id', '-i', help='Backup-id of backup', dest='id', action='store') + group_list_mutually.add_argument('--archived', '-a', help='List only archived backup', dest='archived', + action='store_true') + group_list_mutually.add_argument('--cleaned', '-c', help='List only cleaned backup', dest='cleaned', + action='store_true') + group_list_mutually.add_argument('--computer', '-H', help='List only match hostname or ip', dest='hostname', + action='store') + group_list.add_argument('--oneline', '-o', help='One line output', dest='oneline', action='store_true') + # export session + export_action = action.add_parser('export', help='Export options', parents=[parent_parser]) + group_export = export_action.add_argument_group(title='Export options') + group_export.add_argument('--catalog', '-C', help='Folder where is catalog file', dest='catalog', action='store', + required=True) + group_export.add_argument('--backup-id', '-i', help='Backup-id of backup', dest='id', action='store', required=True) + group_export.add_argument('--destination', '-d', help='Destination path', dest='destination', action='store', + required=True) + group_export.add_argument('--mirror', '-m', help='Mirror mode', dest='mirror', action='store_true') + group_export.add_argument('--cut', '-c', help='Cut mode. Delete source', dest='cut', action='store_true') + group_export_mutually = group_export.add_mutually_exclusive_group() + group_export_mutually.add_argument('--include', '-I', help='Include pattern', dest='include', action='store', + nargs='+') + group_export_mutually.add_argument('--exclude', '-E', help='Exclude pattern', dest='exclude', action='store', + nargs='+') + group_export.add_argument('--timeout', '-T', help='I/O timeout in seconds', dest='timeout', action='store', + type=int) + group_export.add_argument('--skip-error', '-e', help='Skip error', dest='skip_err', action='store_true') # Return all args parser_object.add_argument('--version', '-V', help='Print version', dest='version', action='store_true') return parser_object @@ -887,7 +977,10 @@ def parse_arguments(): + utility.PrintColor.END) continue if not args.verbose: - check_configuration(hostname) + if check_configuration(hostname): + print(utility.PrintColor.RED + '''ERROR: For bulk or silently backup to deploy configuration! + See bb deploy --help or specify --verbose''' + utility.PrintColor.END) + continue # Log information's backup_id = '{}'.format(utility.new_id()) log_args = { @@ -983,7 +1076,10 @@ def parse_arguments(): + utility.PrintColor.END) exit(1) if not args.verbose: - check_configuration(rhost) + if not check_configuration(rhost): + print(utility.PrintColor.RED + '''ERROR: For bulk or silently backup to deploy configuration! + See bb deploy --help or specify --verbose''' + utility.PrintColor.END) + exit(1) log_args = { 'hostname': rhost, 'status': args.log, @@ -1034,39 +1130,73 @@ def parse_arguments(): list_catalog = read_catalog(os.path.join(args.catalog, '.catalog.cfg')) # Check specified argument backup-id if args.id: - utility.print_verbose(args.verbose, "Select backup-id: {0}".format(args.id)) - if not list_catalog.has_section(args.id): - print(utility.PrintColor.RED + - 'ERROR: Backup-id {0} not exist!'.format(args.id) - + utility.PrintColor.END) - exit(1) - print('Backup id: ' + utility.PrintColor.BOLD + args.id + - utility.PrintColor.END) - print('Hostname or ip: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['name'] + - utility.PrintColor.END) - print('Type: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['type'] + - utility.PrintColor.END) - print('Timestamp: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['timestamp'] + - utility.PrintColor.END) - print('Start: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['start'] + - utility.PrintColor.END) - print('Finish: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['end'] + - utility.PrintColor.END) - print('OS: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['os'] + - utility.PrintColor.END) - print('ExitCode: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['status'] + - utility.PrintColor.END) - print('Path: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['path'] + - utility.PrintColor.END) - if list_catalog.get(args.id, 'cleaned', fallback=False): - print('Cleaned: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['cleaned'] + + if not args.oneline: + utility.print_verbose(args.verbose, "Select backup-id: {0}".format(args.id)) + if not list_catalog.has_section(args.id): + print(utility.PrintColor.RED + + 'ERROR: Backup-id {0} not exist!'.format(args.id) + + utility.PrintColor.END) + exit(1) + print('Backup id: ' + utility.PrintColor.BOLD + args.id + utility.PrintColor.END) - elif list_catalog.get(args.id, 'archived', fallback=False): - print('Archived: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['archived'] + + print('Hostname or ip: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['name'] + utility.PrintColor.END) - else: - print('List: ' + utility.PrintColor.DARKCYAN + '\n'.join(os.listdir(list_catalog[args.id]['path'])) + + print('Type: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['type'] + + utility.PrintColor.END) + print('Timestamp: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['timestamp'] + + utility.PrintColor.END) + print('Start: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['start'] + + utility.PrintColor.END) + print('Finish: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['end'] + + utility.PrintColor.END) + print('OS: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['os'] + + utility.PrintColor.END) + print('ExitCode: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['status'] + + utility.PrintColor.END) + print('Path: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['path'] + utility.PrintColor.END) + if list_catalog.get(args.id, 'cleaned', fallback=False): + print('Cleaned: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['cleaned'] + + utility.PrintColor.END) + elif list_catalog.get(args.id, 'archived', fallback=False): + print('Archived: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['archived'] + + utility.PrintColor.END) + else: + print('List: ' + utility.PrintColor.DARKCYAN + '\n'.join(os.listdir(list_catalog[args.id]['path'])) + + utility.PrintColor.END) + else: + if not list_catalog.has_section(args.id): + print(utility.PrintColor.RED + + 'ERROR: Backup-id {0} not exist!'.format(args.id) + + utility.PrintColor.END) + exit(1) + print('Id: ' + utility.PrintColor.BOLD + args.id + + utility.PrintColor.END, end=' - ') + print('Name: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['name'] + + utility.PrintColor.END, end=' - ') + print('Type: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['type'] + + utility.PrintColor.END, end=' - ') + print('Timestamp: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['timestamp'] + + utility.PrintColor.END, end=' - ') + print('Start: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['start'] + + utility.PrintColor.END, end=' - ') + print('Finish: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['end'] + + utility.PrintColor.END, end=' - ') + print('OS: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['os'] + + utility.PrintColor.END, end=' - ') + print('ExitCode: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['status'] + + utility.PrintColor.END, end=' - ') + print('Path: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['path'] + + utility.PrintColor.END, end=' - ') + if list_catalog.get(args.id, 'cleaned', fallback=False): + print('Cleaned: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['cleaned'] + + utility.PrintColor.END, end=' - ') + elif list_catalog.get(args.id, 'archived', fallback=False): + print('Archived: ' + utility.PrintColor.DARKCYAN + list_catalog[args.id]['archived'] + + utility.PrintColor.END, end=' - ') + else: + print('List: ' + utility.PrintColor.DARKCYAN + ' '.join(os.listdir(list_catalog[args.id]['path'])) + + utility.PrintColor.END) elif args.archived: utility.print_verbose(args.verbose, "List all archived backup in catalog") text = 'BUTTERFLY BACKUP CATALOG (ARCHIVED)\n\n' @@ -1112,17 +1242,102 @@ def parse_arguments(): text = 'BUTTERFLY BACKUP CATALOG\n\n' utility.write_log(log_args['status'], log_args['destination'], 'INFO', 'BUTTERFLY BACKUP CATALOG') - for lid in list_catalog.sections(): - utility.write_log(log_args['status'], log_args['destination'], 'INFO', - 'Backup id: {0}'.format(lid)) - utility.write_log(log_args['status'], log_args['destination'], 'INFO', - 'Hostname or ip: {0}'.format(list_catalog[lid]['name'])) - utility.write_log(log_args['status'], log_args['destination'], 'INFO', - 'Timestamp: {0}'.format(list_catalog[lid]['timestamp'])) - text += 'Backup id: {0}'.format(lid) - text += '\n' - text += 'Hostname or ip: {0}'.format(list_catalog[lid]['name']) - text += '\n' - text += 'Timestamp: {0}'.format(list_catalog[lid]['timestamp']) - text += '\n\n' + if args.hostname: + for lid in list_catalog.sections(): + if list_catalog[lid]['name'] == args.hostname: + utility.write_log(log_args['status'], log_args['destination'], 'INFO', + 'Backup id: {0}'.format(lid)) + utility.write_log(log_args['status'], log_args['destination'], 'INFO', + 'Hostname or ip: {0}'.format(list_catalog[lid]['name'])) + utility.write_log(log_args['status'], log_args['destination'], 'INFO', + 'Timestamp: {0}'.format(list_catalog[lid]['timestamp'])) + text += 'Backup id: {0}'.format(lid) + text += '\n' + text += 'Hostname or ip: {0}'.format(list_catalog[lid]['name']) + text += '\n' + text += 'Timestamp: {0}'.format(list_catalog[lid]['timestamp']) + text += '\n\n' + else: + for lid in list_catalog.sections(): + utility.write_log(log_args['status'], log_args['destination'], 'INFO', + 'Backup id: {0}'.format(lid)) + utility.write_log(log_args['status'], log_args['destination'], 'INFO', + 'Hostname or ip: {0}'.format(list_catalog[lid]['name'])) + utility.write_log(log_args['status'], log_args['destination'], 'INFO', + 'Timestamp: {0}'.format(list_catalog[lid]['timestamp'])) + text += 'Backup id: {0}'.format(lid) + text += '\n' + text += 'Hostname or ip: {0}'.format(list_catalog[lid]['name']) + text += '\n' + text += 'Timestamp: {0}'.format(list_catalog[lid]['timestamp']) + text += '\n\n' utility.pager(text) + + # Check export session + if args.action == 'export': + cmds = list() + # Read catalog file + export_catalog = read_catalog(os.path.join(args.catalog, '.catalog.cfg')) + # Check specified argument backup-id + if not export_catalog.has_section(args.id): + print(utility.PrintColor.RED + + 'ERROR: Backup-id {0} not exist!'.format(args.id) + + utility.PrintColor.END) + exit(1) + print_verbose(args.verbose, 'Export backup with id {0}'.format(args.id)) + # Log info + log_args = { + 'hostname': export_catalog[args.id]['Name'], + 'status': args.log, + 'destination': os.path.join(export_catalog[args.id]['Path'], 'export.log') + } + if os.path.exists(export_catalog[args.id]['Path']) and os.path.exists(args.destination): + # Export + utility.write_log(log_args['status'], log_args['destination'], 'INFO', + 'Export {0}. Folder {1} to {2}'.format(args.id, export_catalog[args.id]['Path'], + args.destination)) + # Compose command + print_verbose(args.verbose, 'Build a rsync command') + logs = list() + logs.append(log_args) + # Set rsync binary and flags + expcmd = list() + expcmd.append('rsync') + expcmd.append('-ah') + expcmd.append('--no-links') + # Check flags + if args.verbose: + expcmd.append('-vP') + if args.mirror: + expcmd.append('--delete') + if args.cut: + expcmd.append('--remove-source-files') + if args.include: + for include in args.include: + expcmd.append('--include={0}'.format(include)) + expcmd.append('--exclude="*"') + if args.exclude: + for exclude in args.exclude: + expcmd.append('--exclude={0}'.format(exclude)) + if args.timeout: + expcmd.append('--timeout={0}'.format(args.timeout)) + if args.skip_err: + expcmd.append('--quiet') + if args.log: + exp_log_path = os.path.join(args.destination, 'export.log') + expcmd.append( + '--log-file={0}'.format(exp_log_path) + ) + # Add source + expcmd.append('{}'.format(export_catalog[args.id]['Path'])) + # Add destination + expcmd.append('{}'.format(os.path.join(args.destination, export_catalog[args.id]['Name']))) + utility.write_log(log_args['status'], log_args['destination'], 'INFO', + 'Export command {0}.'.format(" ".join(expcmd))) + # Start export + cmds.append(' '.join(expcmd)) + run_in_parallel(start_process, cmds, 1) + else: + print(utility.PrintColor.RED + + "ERROR: Source or destination path doesn't exist!" + utility.PrintColor.END) + exit(1) diff --git a/docs/Makefile b/docs/Makefile old mode 100644 new mode 100755 diff --git a/docs/build/doctrees/backup.doctree b/docs/build/doctrees/backup.doctree old mode 100644 new mode 100755 diff --git a/docs/build/doctrees/environment.pickle b/docs/build/doctrees/environment.pickle old mode 100644 new mode 100755 index c3b19a4..612f6ca Binary files a/docs/build/doctrees/environment.pickle and b/docs/build/doctrees/environment.pickle differ diff --git a/docs/build/doctrees/index.doctree b/docs/build/doctrees/index.doctree old mode 100644 new mode 100755 index 24519ed..5a0f179 Binary files a/docs/build/doctrees/index.doctree and b/docs/build/doctrees/index.doctree differ diff --git a/docs/build/html/.buildinfo b/docs/build/html/.buildinfo old mode 100644 new mode 100755 index 48c32cb..142f6b0 --- a/docs/build/html/.buildinfo +++ b/docs/build/html/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 5a0ff6af6cf626bd878c7dc1cd68852d +config: fcd3a7349e495b52c72a060fa709375b tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/build/html/_sources/backup.rst.txt b/docs/build/html/_sources/backup.rst.txt old mode 100644 new mode 100755 diff --git a/docs/build/html/_sources/index.rst.txt b/docs/build/html/_sources/index.rst.txt old mode 100644 new mode 100755 index cac52aa..799efea --- a/docs/build/html/_sources/index.rst.txt +++ b/docs/build/html/_sources/index.rst.txt @@ -22,6 +22,7 @@ Indices and tables * :ref:`list` * :ref:`restore` * :ref:`archive` +* :ref:`export` * :ref:`search` .. _requirements: @@ -41,10 +42,11 @@ Install Butterfly Backup is very simple; run this: .. code-block:: bash - arthur@goldenheart$ git clone https://github.com/MatteoGuadrini/Butterfly-Backup.git - arthur@goldenheart$ cd Butterfly-Backup - arthur@goldenheart$ sudo python3 setup.py - arthur@goldenheart$ bb --help + arthur@heartofgold$ git clone https://github.com/MatteoGuadrini/Butterfly-Backup.git + arthur@heartofgold$ cd Butterfly-Backup + arthur@heartofgold$ sudo python3 setup.py + arthur@heartofgold$ bb --help + arthur@heartofgold$ man bb The upgrade is also simple; type the same commands. @@ -62,7 +64,7 @@ Backups are organized according to precise cataloguing; this is an example: .. code-block:: bash - arthur@goldenheart$ tree destination/of/backup + arthur@heartofgold$ tree destination/of/backup . ├── destination │   ├── hostname or ip of the PC under backup @@ -91,13 +93,13 @@ This is how the operating system sees the backups on the file system: .. important:: Be careful to not delete *.catalog.cfg* file. -Backup butterfly has, in its core, five main operations: +Butterfly Backup has, in its core, six main operations: .. code-block:: bash - arthur@goldenheart$ bb --help + arthur@heartofgold$ bb --help usage: bb [-h] [--verbose] [--log] [--dry-run] [--version] - {config,backup,restore,archive,list} ... + {config,backup,restore,archive,list,export} ... Butterfly Backup @@ -111,13 +113,14 @@ Backup butterfly has, in its core, five main operations: action: Valid action - {config,backup,restore,archive,list} + {config,backup,restore,archive,list,export} Available actions config Configuration options backup Backup options restore Restore options archive Archive options list List options + export Export options Valid action @@ -126,6 +129,7 @@ Valid action * **restore**: transactions that call rsync for pushing files to a client. * **archive**: transaction that zip old backups and move them to a new destination. * **list**: query the catalog. +* **export**: export a single backup to other path. It also has three flags that can be very useful, especially in case of error. @@ -148,7 +152,7 @@ Let's see how to go about looking at the help: .. code-block:: bash - arthur@goldenheart$ bb config --help + arthur@heartofgold$ bb config --help usage: bb config [-h] [--verbose] [--log] [--dry-run] [--new | --remove] [--deploy DEPLOY_HOST] [--user DEPLOY_USER] @@ -181,7 +185,7 @@ At this point, we create the configuration: .. code-block:: bash - arthur@goldenheart$ bb config --new + arthur@heartofgold$ bb config --new WARNING: Private key ~/.ssh/id_rsa exists If you want to use the existing key, run "bb config --deploy name_of_machine", otherwise to remove it, run "bb config --remove" @@ -189,10 +193,10 @@ In this case, the RSA key already exists. Now try delete and create a new keys: .. code-block:: bash - arthur@goldenheart$ bb config --remove + arthur@heartofgold$ bb config --remove Are you sure to remove existing rsa keys? To continue [Y/N]? y SUCCESS: Removed configuration successfully! - arthur@goldenheart$ bb config --new + arthur@heartofgold$ bb config --new SUCCESS: New configuration successfully created! Once you have created the configuration, keys should be installed (copied) on the hosts you @@ -200,8 +204,7 @@ want to backup. .. code-block:: bash - " - arthur@goldenheart$ bb config --deploy host1 + arthur@heartofgold$ bb config --deploy host1 Copying configuration to host1; write the password: /usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/arthur/.ssh/id_rsa.pub" /usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed @@ -214,14 +217,13 @@ want to backup. and check to make sure that only the key(s) you wanted were added. SUCCESS: Configuration copied successfully on host1! - " This command will try to copy the configuration with the current user. If you want to use a different user (e.g.: root), run this: .. code-block:: bash - arthur@goldenheart$ bb config --deploy host1 --user root + arthur@heartofgold$ bb config --deploy host1 --user root "Copying configuration to host1; write the password:" /usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/arthur/.ssh/id_rsa.pub" /usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed @@ -269,7 +271,7 @@ Download https://cygwin.com/ cygwin and follow this instructions to install the C: chdir C:\cygwin\bin - mkpasswd -l > C:\cygwin\etc\passwd + mkpasswd -cl > C:\cygwin\etc\passwd bash ssh-host-config -y cygrunsrv -S sshd @@ -287,10 +289,10 @@ There are two backup modes: single and bulk. Let's see how to go about looking a .. code-block:: bash - arthur@goldenheart$ bb backup --help + arthur@heartofgold$ bb backup --help usage: bb backup [-h] [--verbose] [--log] [--dry-run] (--computer HOSTNAME | --list LIST) --destination DESTINATION - [--mode {Full,Incremental,Mirror}] + [--mode {Full,Incremental,Differential,Mirror}] (--data {User,Config,Application,System,Log} [{User,Config,Application,System,Log} ...] | --custom-data CUSTOMDATA [CUSTOMDATA ...]) [--user USER] --type {Unix,Windows,MacOS} [--compress] [--retention RETENTION] [--parallel PARALLEL] @@ -308,7 +310,7 @@ There are two backup modes: single and bulk. Let's see how to go about looking a --list LIST, -L LIST File list of computers or ip addresses to backup --destination DESTINATION, -d DESTINATION Destination path - --mode {Full,Incremental,Mirror}, -m {Full,Incremental,Mirror} + --mode {Full,Incremental,Differential,Mirror}, -m {Full,Incremental,Differential,Mirror} Backup mode --data {User,Config,Application,System,Log} [{User,Config,Application,System,Log} ...], -D {User,Config,Application,System,Log} [{User,Config,Application,System,Log} ...] Data of which you want to backup @@ -325,6 +327,8 @@ There are two backup modes: single and bulk. Let's see how to go about looking a Number of parallel jobs --timeout TIMEOUT, -T TIMEOUT I/O timeout in seconds + --skip-error, -e Skip error + * **Backup options** @@ -340,13 +344,19 @@ There are two backup modes: single and bulk. Let's see how to go about looking a host2 ... - --destination Select the destination folder (root). + --destination, -d Select the destination folder (root). --mode, -m Select how rsync perform backup: - * Full: Complete (full) backup. - * Incremental: Incremental backup is based on the latest Full (the same files are linked with hard link). + * Full: + Complete (full) backup. + * Incremental: + Incremental backup is based on the latest backup (the same files are linked with hard link). + A Full backup is executed if this mode fails to find one. + * Differential: + Incremental backup is based on the latest Full backup (the same files are linked with hard link). A Full backup is executed if this mode fails to find one. - * Mirror: Complete mirror backup. If a file in the source no longer exists, BB deletes it from the destination. + * Mirror: + Complete mirror backup. If a file in the source no longer exists, BB deletes it from the destination. --data, -D Select the type of data to put under backup: The values change depending on the type of operating system: @@ -366,6 +376,23 @@ There are two backup modes: single and bulk. Let's see how to go about looking a --retention, -r Number of days for which you want to keep your backups. --parallel, -p Maximum number of concurrent rsync processes. By default is 5 jobs. --timeout, -T Specify number of seconds of I/O timeout. + --skip-error, -e Skip error. Quiet mode. + +Flowchart of the differences between Differential and Incremental backup:: + + +----------+ +----------+ +----------+ + | | | | | | + | Full | <-+ Inc. | <-+ Inc. | + | | | | | | + +----------+ +----------+ +----------+ + + +----------+ +----------+ +----------+ + | | | | | | + | Full | <-+ Dif. | | Dif. | + | | | | | | + +----+-----+ +----------+ +----+-----+ + ^ | + +-----------------------------+ .. _backup_computer: @@ -380,7 +407,7 @@ This is a few examples: .. code-block:: bash - arthur@goldenheart$ bb backup --computer host1 --destination /mnt/backup --data User Config --type MacOS + arthur@heartofgold$ bb backup --computer host1 --destination /mnt/backup --data User Config --type MacOS # host1 SSH-2.0-OpenSSH_7.5 # host1 SSH-2.0-OpenSSH_7.5 Start backup on host1 @@ -392,14 +419,14 @@ This is a few examples: .. code-block:: bash - arthur@goldenheart$ bb backup --computer host1 --destination /mnt/backup --mode Full --data User Config --type MacOS --user root + arthur@heartofgold$ bb backup --computer host1 --destination /mnt/backup --mode Full --data User Config --type MacOS --user root "root@host1's password:" Start backup on host1 SUCCESS: Command rsync -ah --no-links root@host1:/Users :/private/etc /mnt/backup/host1/2018_08_08__10_30 .. code-block:: bash - arthur@goldenheart$ bb backup --computer host1 --destination /mnt/backup --data User Config --type MacOS --verbose --log + arthur@heartofgold$ bb backup --computer host1 --destination /mnt/backup --data User Config --type MacOS --verbose --log INFO: Build a rsync command INFO: Last full is 2018-08-08 10:30:32 INFO: Command flags are: rsync -ahu --no-links --link-dest=/mnt/backup/host1/2018_08_08__10_28 -vP @@ -433,7 +460,7 @@ This is a few examples: .. code-block:: bash - arthur@goldenheart$ bb backup --list /home/arthur/pclist.txt --destination /mnt/backup --data User Config --type MacOS + arthur@heartofgold$ bb backup --list /home/arthur/pclist.txt --destination /mnt/backup --data User Config --type MacOS # host1 SSH-2.0-OpenSSH_7.5 # host1 SSH-2.0-OpenSSH_7.5 ERROR: The port 22 on host2 is closed! @@ -452,7 +479,7 @@ This means that maximum two backup jobs will run at the same time. When a first .. code-block:: bash - arthur@goldenheart$ bb backup --list /home/arthur/pclist.txt --destination /mnt/backup --data User Config --type MacOS --parallel 2 --log + arthur@heartofgold$ bb backup --list /home/arthur/pclist.txt --destination /mnt/backup --data User Config --type MacOS --parallel 2 --log # host1 SSH-2.0-OpenSSH_7.5 # host1 SSH-2.0-OpenSSH_7.5 ERROR: The port 22 on host2 is closed! @@ -464,7 +491,7 @@ This is the same example but specifying a retention at 3 (days). This means that .. code-block:: bash - arthur@goldenheart$ bb backup --list /home/arthur/pclist.txt --destination /mnt/backup --data User Config --type MacOS --parallel 2 --retention 3 --log --verbose + arthur@heartofgold$ bb backup --list /home/arthur/pclist.txt --destination /mnt/backup --data User Config --type MacOS --parallel 2 --retention 3 --log --verbose INFO: Build a rsync command INFO: Last full is 2018-08-08 10:30:32 INFO: Command flags are: rsync -ahu --no-links --link-dest=/mnt/backup/host1/2018_08_08__10_30 -vP @@ -504,9 +531,10 @@ To query this catalog, the list command exists. .. code-block:: bash - arthur@goldenheart$ bb list --help + arthur@heartofgold$ bb list --help usage: bb list [-h] [--verbose] [--log] [--dry-run] --catalog CATALOG - [--backup-id ID] [--archived] [--cleaned] + [--backup-id ID | --archived | --cleaned | --computer HOSTNAME] + [--oneline] optional arguments: -h, --help show this help message and exit @@ -521,18 +549,24 @@ To query this catalog, the list command exists. Backup-id of backup --archived, -a List only archived backup --cleaned, -c List only cleaned backup + --computer HOSTNAME, -H HOSTNAME + List only match hostname or ip + --oneline, -o One line output + * **List options** --catalog, -C Select the backups folder (root). --backup-id, -i Select backup id in the catalog. --archived, -a List only archived backups. --cleaned, -c List only cleaned backups. + --computer, -H List only match hostname or ip. + --oneline, -o One line and concise output. First, let's query the catalog: .. code-block:: bash - arthur@goldenheart$ bb list --catalog /mnt/backup + arthur@heartofgold$ bb list --catalog /mnt/backup BUTTERFLY BACKUP CATALOG @@ -552,7 +586,7 @@ Press ``q`` for exit. Then we select a backup-id: .. code-block:: bash - arthur@goldenheart$ bb list --catalog /mnt/backup --backup-id dd6de2f2-9a1e-11e8-82b0-005056a664e0 + arthur@heartofgold$ bb list --catalog /mnt/backup --backup-id dd6de2f2-9a1e-11e8-82b0-005056a664e0 Backup id: dd6de2f2-9a1e-11e8-82b0-005056a664e0 Hostname or ip: host1 Type: Incremental @@ -570,8 +604,8 @@ If you want to export the catalog list instead, include the log flag: .. code-block:: bash - arthur@goldenheart$ bb list --catalog /mnt/backup --log - arthur@goldenheart$ cat /mnt/backup/backup.list + arthur@heartofgold$ bb list --catalog /mnt/backup --log + arthur@heartofgold$ cat /mnt/backup/backup.list Now that we have identified a backup, let's proceed with the restore @@ -585,7 +619,7 @@ The restore process is the exact opposite of the backup process. It takes the fi .. code-block:: bash - arthur@goldenheart$ bb restore --help + arthur@heartofgold$ bb restore --help usage: bb restore [-h] [--verbose] [--log] [--dry-run] --catalog CATALOG (--backup-id ID | --last) [--user USER] --computer HOSTNAME [--type {Unix,Windows,MacOS}] [--timeout TIMEOUT] [--mirror] @@ -611,6 +645,7 @@ The restore process is the exact opposite of the backup process. It takes the fi --timeout TIMEOUT, -T TIMEOUT I/O timeout in seconds --mirror, -m Mirror mode + --skip-error, -e Skip error * **Restore options** --catalog, -C Select the backups folder (root). @@ -626,6 +661,8 @@ The restore process is the exact opposite of the backup process. It takes the fi --timeout, -T Specify number of seconds of I/O timeout. --mirror, -m Mirror mode. If a file or folder not exist in destination, will delete it. Overwrite files. + --skip-error, -e Skip error. Quiet mode. + This is a few examples: @@ -633,7 +670,7 @@ This command perform a restore on the same machine of the backup: .. code-block:: bash - arthur@goldenheart$ bb restore --catalog /mnt/backup --backup-id dd6de2f2-9a1e-11e8-82b0-005056a664e0 --computer host1 --log + arthur@heartofgold$ bb restore --catalog /mnt/backup --backup-id dd6de2f2-9a1e-11e8-82b0-005056a664e0 --computer host1 --log Want to do restore path /mnt/backup/host1/2018_08_08__10_58/etc? To continue [Y/N]? y Want to do restore path /mnt/backup/host1/2018_08_08__10_58/Users? To continue [Y/N]? y SUCCESS: Command rsync -ahu -vP --log-file=/mnt/backup/host1/2018_08_08__10_58/restore.log /mnt/backup/host1/2018_08_08__10_58/etc arthur@host1:/restore_2018_08_08__10_58 @@ -647,7 +684,7 @@ Now, select the last available backup on catalog; run this: .. code-block:: bash - arthur@goldenheart$ bb restore --catalog /mnt/backup --last --computer host1 --log + arthur@heartofgold$ bb restore --catalog /mnt/backup --last --computer host1 --log Want to do restore path /mnt/backup/host1/2018_08_08__10_58/etc? To continue [Y/N]? y Want to do restore path /mnt/backup/host1/2018_08_08__10_58/Users? To continue [Y/N]? y SUCCESS: Command rsync -ahu -vP --log-file=/mnt/backup/host1/2018_08_08__10_58/restore.log /mnt/backup/host1/2018_08_08__10_58/etc arthur@host1:/restore_2018_08_08__10_59 @@ -657,7 +694,7 @@ This example, is the same as the previous one, but restore to other machine and .. code-block:: bash - arthur@goldenheart$ bb restore --catalog /mnt/backup --backup-id dd6de2f2-9a1e-11e8-82b0-005056a664e0 --computer host2 --type Unix --log --verbose + arthur@heartofgold$ bb restore --catalog /mnt/backup --backup-id dd6de2f2-9a1e-11e8-82b0-005056a664e0 --computer host2 --type Unix --log --verbose INFO: Build a rsync command INFO: Command flags are: rsync -ahu -vP --log-file=/mnt/backup/host1/2018_08_08__10_58/restore.log Want to do restore path /mnt/backup/host1/2018_08_08__10_58/etc? To continue [Y/N]? y @@ -687,7 +724,7 @@ Archive operations are used to store backups by saving disk space. Backups older .. code-block:: bash - arthur@goldenheart$ bb archive --help + arthur@heartofgold$ bb archive --help usage: bb archive [-h] [--verbose] [--log] [--dry-run] --catalog CATALOG [--days DAYS] --destination DESTINATION @@ -708,20 +745,20 @@ Archive operations are used to store backups by saving disk space. Backups older * **Archive options** --catalog, -C Select the backups folder (root). --days, -D Number of days for which you want to keep your backups. Default is 30. - --destination New destination for compress zipped backup. + --destination, -d New destination for compress zipped backup. Archive old backup of 3 days: .. code-block:: bash - arthur@goldenheart$ bb archive --catalog /mnt/backup/ --days 3 --destination /mnt/archive/ --verbose --log + arthur@heartofgold$ bb archive --catalog /mnt/backup/ --days 3 --destination /mnt/archive/ --verbose --log INFO: Check archive this backup f65e5afe-9734-11e8-b0bb-005056a664e0. Folder /mnt/backup/host2/2018_08_08__17_50 INFO: Check archive this backup 4f2b5f6e-9939-11e8-9ab6-005056a664e0. Folder /mnt/backup/host2/2018_08_04__07_26 SUCCESS: Delete /mnt/backup/host2/2018_08_04__07_26 successfully. SUCCESS: Archive /mnt/backup/host2/2018_08_04__07_26 successfully. - arthur@goldenheart$ ls /mnt/archive + arthur@heartofgold$ ls /mnt/archive host1 - arthur@goldenheart$ ls /mnt/archive/host1 + arthur@heartofgold$ ls /mnt/archive/host1 2018_08_06__07_26.zip The backup-id *f65e5afe-9734-11e8-b0bb-005056a664e0* it is not considered, because it falls within the established time. @@ -731,7 +768,7 @@ Lastly, let's look in the catalog and see that the backup was actually archived: .. code-block:: bash - arthur@goldenheart$ bb list --catalog /mnt/backup/ -i 4f2b5f6e-9939-11e8-9ab6-005056a664e0 + arthur@heartofgold$ bb list --catalog /mnt/backup/ -i 4f2b5f6e-9939-11e8-9ab6-005056a664e0 Backup id: 4f2b5f6e-9939-11e8-9ab6-005056a664e0 Hostname or ip: host2 Type: Incremental @@ -742,3 +779,76 @@ Lastly, let's look in the catalog and see that the backup was actually archived: ExitCode: 0 Path: /mnt/backup/host2/2018_08_04__07_26 Archived: True + +.. _export: + +###### +Export +###### + +The export function is used to copy a particular backup to another path. + +.. code-block:: bash + + arthur@heartofgold$ bb export -h + usage: bb export [-h] [--verbose] [--log] [--dry-run] --catalog CATALOG + --backup-id ID --destination DESTINATION [--mirror] [--cut] + [--include INCLUDE [INCLUDE ...]] + [--exclude EXCLUDE [EXCLUDE ...]] [--timeout TIMEOUT] + [--skip-error] + + optional arguments: + -h, --help show this help message and exit + --verbose, -v Enable verbosity + --log, -l Create a log + --dry-run, -N Dry run mode + + Export options: + --catalog CATALOG, -C CATALOG + Folder where is catalog file + --backup-id ID, -i ID + Backup-id of backup + --destination DESTINATION, -d DESTINATION + Destination path + --mirror, -m Mirror mode + --cut, -c Cut mode. Delete source + --include INCLUDE [INCLUDE ...], -I INCLUDE [INCLUDE ...] + Include pattern + --exclude EXCLUDE [EXCLUDE ...], -E EXCLUDE [EXCLUDE ...] + Exclude pattern + --timeout TIMEOUT, -T TIMEOUT + I/O timeout in seconds + --skip-error, -e Skip error + +* **Export options** + --catalog, -C Select the backups folder (root). + --backup-id, -i Select backup id in the catalog. + --destination, -d Destination path than export a backup. + --mirror, -m Mirror mode. + --cut, -c Cut mode. Delete source. Like a move function. + --include, -I Include pattern. Accept wildcard character. + --exclude, -E Exclude pattern. Accept wildcard character. + --timeout, -T Specify number of seconds of I/O timeout. + --skip-error, -e Skip error. Quiet mode. + +Export a backup in other directory: + +.. code-block:: bash + + arthur@heartofgold$ bb export --catalog /mnt/backup/ --backup-id f0f700e8-0435-11e9-9e78-005056a664e0 --destination /mnt/backup/export --verbose + INFO: Export backup with id f0f700e8-0435-11e9-9e78-005056a664e0 + INFO: Build a rsync command + Start export host1 + INFO: rsync command: rsync -ah --no-links -vP /mnt/backup/host1/2018_12_20__10_02 /mnt/backup/export/host1 + SUCCESS: Command rsync -ah --no-links -vP /mnt/backup/host1/2018_12_20__10_02 /mnt/backup/export/host1 + +Export a backup with exclude pdf files: + +.. code-block:: bash + + arthur@heartofgold$ bb export --catalog /mnt/backup/ --backup-id f0f700e8-0435-11e9-9e78-005056a664e0 --destination /backup/export --verbose --exclude *.pdf + INFO: Export backup with id f0f700e8-0435-11e9-9e78-005056a664e0 + INFO: Build a rsync command + Start export host1 + INFO: rsync command: rsync -ah --no-links -vP --exclude=*.pdf /mnt/backup/host1/2018_12_20__10_02 /mnt/backup/export/host1 + SUCCESS: Command rsync -ah --no-links -vP --exclude=*.pdf /mnt/backup/host1/2018_12_20__10_02 /mnt/backup/export/host1 diff --git a/docs/build/html/_static/ajax-loader.gif b/docs/build/html/_static/ajax-loader.gif old mode 100644 new mode 100755 diff --git a/docs/build/html/_static/alabaster.css b/docs/build/html/_static/alabaster.css old mode 100644 new mode 100755 diff --git a/docs/build/html/_static/basic.css b/docs/build/html/_static/basic.css old mode 100644 new mode 100755 index 19ced10..104f076 --- a/docs/build/html/_static/basic.css +++ b/docs/build/html/_static/basic.css @@ -81,6 +81,10 @@ div.sphinxsidebar input { font-size: 1em; } +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + div.sphinxsidebar #searchbox input[type="text"] { float: left; width: 80%; @@ -427,6 +431,13 @@ table.field-list td, table.field-list th { hyphens: manual; } +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist td { + vertical-align: top; +} + + /* -- other body styles ----------------------------------------------------- */ ol.arabic { diff --git a/docs/build/html/_static/comment-bright.png b/docs/build/html/_static/comment-bright.png old mode 100644 new mode 100755 diff --git a/docs/build/html/_static/comment-close.png b/docs/build/html/_static/comment-close.png old mode 100644 new mode 100755 diff --git a/docs/build/html/_static/comment.png b/docs/build/html/_static/comment.png old mode 100644 new mode 100755 diff --git a/docs/build/html/_static/custom.css b/docs/build/html/_static/custom.css old mode 100644 new mode 100755 diff --git a/docs/build/html/_static/doctools.js b/docs/build/html/_static/doctools.js old mode 100644 new mode 100755 index d892892..ffadbec --- a/docs/build/html/_static/doctools.js +++ b/docs/build/html/_static/doctools.js @@ -150,7 +150,9 @@ var Documentation = { this.fixFirefoxAnchorBug(); this.highlightSearchWords(); this.initIndexTable(); - + if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { + this.initOnKeyListeners(); + } }, /** @@ -310,4 +312,4 @@ _ = Documentation.gettext; $(document).ready(function() { Documentation.init(); -}); \ No newline at end of file +}); diff --git a/docs/build/html/_static/documentation_options.js b/docs/build/html/_static/documentation_options.js old mode 100644 new mode 100755 index 87ce200..b81190b --- a/docs/build/html/_static/documentation_options.js +++ b/docs/build/html/_static/documentation_options.js @@ -5,5 +5,292 @@ var DOCUMENTATION_OPTIONS = { COLLAPSE_INDEX: false, FILE_SUFFIX: '.html', HAS_SOURCE: true, - SOURCELINK_SUFFIX: '.txt' -}; \ No newline at end of file + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SEARCH_LANGUAGE_STOP_WORDS: ["a","and","are","as","at","be","but","by","for","if","in","into","is","it","near","no","not","of","on","or","such","that","the","their","then","there","these","they","this","to","was","will","with"] +}; + + + +/* Non-minified version JS is _stemmer.js if file is provided */ +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + + + + + +var splitChars = (function() { + var result = {}; + var singles = [96, 180, 187, 191, 215, 247, 749, 885, 903, 907, 909, 930, 1014, 1648, + 1748, 1809, 2416, 2473, 2481, 2526, 2601, 2609, 2612, 2615, 2653, 2702, + 2706, 2729, 2737, 2740, 2857, 2865, 2868, 2910, 2928, 2948, 2961, 2971, + 2973, 3085, 3089, 3113, 3124, 3213, 3217, 3241, 3252, 3295, 3341, 3345, + 3369, 3506, 3516, 3633, 3715, 3721, 3736, 3744, 3748, 3750, 3756, 3761, + 3781, 3912, 4239, 4347, 4681, 4695, 4697, 4745, 4785, 4799, 4801, 4823, + 4881, 5760, 5901, 5997, 6313, 7405, 8024, 8026, 8028, 8030, 8117, 8125, + 8133, 8181, 8468, 8485, 8487, 8489, 8494, 8527, 11311, 11359, 11687, 11695, + 11703, 11711, 11719, 11727, 11735, 12448, 12539, 43010, 43014, 43019, 43587, + 43696, 43713, 64286, 64297, 64311, 64317, 64319, 64322, 64325, 65141]; + var i, j, start, end; + for (i = 0; i < singles.length; i++) { + result[singles[i]] = true; + } + var ranges = [[0, 47], [58, 64], [91, 94], [123, 169], [171, 177], [182, 184], [706, 709], + [722, 735], [741, 747], [751, 879], [888, 889], [894, 901], [1154, 1161], + [1318, 1328], [1367, 1368], [1370, 1376], [1416, 1487], [1515, 1519], [1523, 1568], + [1611, 1631], [1642, 1645], [1750, 1764], [1767, 1773], [1789, 1790], [1792, 1807], + [1840, 1868], [1958, 1968], [1970, 1983], [2027, 2035], [2038, 2041], [2043, 2047], + [2070, 2073], [2075, 2083], [2085, 2087], [2089, 2307], [2362, 2364], [2366, 2383], + [2385, 2391], [2402, 2405], [2419, 2424], [2432, 2436], [2445, 2446], [2449, 2450], + [2483, 2485], [2490, 2492], [2494, 2509], [2511, 2523], [2530, 2533], [2546, 2547], + [2554, 2564], [2571, 2574], [2577, 2578], [2618, 2648], [2655, 2661], [2672, 2673], + [2677, 2692], [2746, 2748], [2750, 2767], [2769, 2783], [2786, 2789], [2800, 2820], + [2829, 2830], [2833, 2834], [2874, 2876], [2878, 2907], [2914, 2917], [2930, 2946], + [2955, 2957], [2966, 2968], [2976, 2978], [2981, 2983], [2987, 2989], [3002, 3023], + [3025, 3045], [3059, 3076], [3130, 3132], [3134, 3159], [3162, 3167], [3170, 3173], + [3184, 3191], [3199, 3204], [3258, 3260], [3262, 3293], [3298, 3301], [3312, 3332], + [3386, 3388], [3390, 3423], [3426, 3429], [3446, 3449], [3456, 3460], [3479, 3481], + [3518, 3519], [3527, 3584], [3636, 3647], [3655, 3663], [3674, 3712], [3717, 3718], + [3723, 3724], [3726, 3731], [3752, 3753], [3764, 3772], [3774, 3775], [3783, 3791], + [3802, 3803], [3806, 3839], [3841, 3871], [3892, 3903], [3949, 3975], [3980, 4095], + [4139, 4158], [4170, 4175], [4182, 4185], [4190, 4192], [4194, 4196], [4199, 4205], + [4209, 4212], [4226, 4237], [4250, 4255], [4294, 4303], [4349, 4351], [4686, 4687], + [4702, 4703], [4750, 4751], [4790, 4791], [4806, 4807], [4886, 4887], [4955, 4968], + [4989, 4991], [5008, 5023], [5109, 5120], [5741, 5742], [5787, 5791], [5867, 5869], + [5873, 5887], [5906, 5919], [5938, 5951], [5970, 5983], [6001, 6015], [6068, 6102], + [6104, 6107], [6109, 6111], [6122, 6127], [6138, 6159], [6170, 6175], [6264, 6271], + [6315, 6319], [6390, 6399], [6429, 6469], [6510, 6511], [6517, 6527], [6572, 6592], + [6600, 6607], [6619, 6655], [6679, 6687], [6741, 6783], [6794, 6799], [6810, 6822], + [6824, 6916], [6964, 6980], [6988, 6991], [7002, 7042], [7073, 7085], [7098, 7167], + [7204, 7231], [7242, 7244], [7294, 7400], [7410, 7423], [7616, 7679], [7958, 7959], + [7966, 7967], [8006, 8007], [8014, 8015], [8062, 8063], [8127, 8129], [8141, 8143], + [8148, 8149], [8156, 8159], [8173, 8177], [8189, 8303], [8306, 8307], [8314, 8318], + [8330, 8335], [8341, 8449], [8451, 8454], [8456, 8457], [8470, 8472], [8478, 8483], + [8506, 8507], [8512, 8516], [8522, 8525], [8586, 9311], [9372, 9449], [9472, 10101], + [10132, 11263], [11493, 11498], [11503, 11516], [11518, 11519], [11558, 11567], + [11622, 11630], [11632, 11647], [11671, 11679], [11743, 11822], [11824, 12292], + [12296, 12320], [12330, 12336], [12342, 12343], [12349, 12352], [12439, 12444], + [12544, 12548], [12590, 12592], [12687, 12689], [12694, 12703], [12728, 12783], + [12800, 12831], [12842, 12880], [12896, 12927], [12938, 12976], [12992, 13311], + [19894, 19967], [40908, 40959], [42125, 42191], [42238, 42239], [42509, 42511], + [42540, 42559], [42592, 42593], [42607, 42622], [42648, 42655], [42736, 42774], + [42784, 42785], [42889, 42890], [42893, 43002], [43043, 43055], [43062, 43071], + [43124, 43137], [43188, 43215], [43226, 43249], [43256, 43258], [43260, 43263], + [43302, 43311], [43335, 43359], [43389, 43395], [43443, 43470], [43482, 43519], + [43561, 43583], [43596, 43599], [43610, 43615], [43639, 43641], [43643, 43647], + [43698, 43700], [43703, 43704], [43710, 43711], [43715, 43738], [43742, 43967], + [44003, 44015], [44026, 44031], [55204, 55215], [55239, 55242], [55292, 55295], + [57344, 63743], [64046, 64047], [64110, 64111], [64218, 64255], [64263, 64274], + [64280, 64284], [64434, 64466], [64830, 64847], [64912, 64913], [64968, 65007], + [65020, 65135], [65277, 65295], [65306, 65312], [65339, 65344], [65371, 65381], + [65471, 65473], [65480, 65481], [65488, 65489], [65496, 65497]]; + for (i = 0; i < ranges.length; i++) { + start = ranges[i][0]; + end = ranges[i][1]; + for (j = start; j <= end; j++) { + result[j] = true; + } + } + return result; +})(); + +function splitQuery(query) { + var result = []; + var start = -1; + for (var i = 0; i < query.length; i++) { + if (splitChars[query.charCodeAt(i)]) { + if (start !== -1) { + result.push(query.slice(start, i)); + start = -1; + } + } else if (start === -1) { + start = i; + } + } + if (start !== -1) { + result.push(query.slice(start)); + } + return result; +} + + diff --git a/docs/build/html/_static/down-pressed.png b/docs/build/html/_static/down-pressed.png old mode 100644 new mode 100755 diff --git a/docs/build/html/_static/down.png b/docs/build/html/_static/down.png old mode 100644 new mode 100755 diff --git a/docs/build/html/_static/file.png b/docs/build/html/_static/file.png old mode 100644 new mode 100755 diff --git a/docs/build/html/_static/jquery-3.2.1.js b/docs/build/html/_static/jquery-3.2.1.js old mode 100644 new mode 100755 diff --git a/docs/build/html/_static/jquery.js b/docs/build/html/_static/jquery.js old mode 100644 new mode 100755 diff --git a/docs/build/html/_static/minus.png b/docs/build/html/_static/minus.png old mode 100644 new mode 100755 diff --git a/docs/build/html/_static/plus.png b/docs/build/html/_static/plus.png old mode 100644 new mode 100755 diff --git a/docs/build/html/_static/pygments.css b/docs/build/html/_static/pygments.css old mode 100644 new mode 100755 diff --git a/docs/build/html/_static/searchtools.js b/docs/build/html/_static/searchtools.js old mode 100644 new mode 100755 index 41b8336..7473859 --- a/docs/build/html/_static/searchtools.js +++ b/docs/build/html/_static/searchtools.js @@ -1,5 +1,5 @@ /* - * searchtools.js_t + * searchtools.js * ~~~~~~~~~~~~~~~~ * * Sphinx JavaScript utilities for the full-text search. @@ -9,323 +9,44 @@ * */ - -/* Non-minified version JS is _stemmer.js if file is provided */ -/** - * Porter Stemmer - */ -var Stemmer = function() { - - var step2list = { - ational: 'ate', - tional: 'tion', - enci: 'ence', - anci: 'ance', - izer: 'ize', - bli: 'ble', - alli: 'al', - entli: 'ent', - eli: 'e', - ousli: 'ous', - ization: 'ize', - ation: 'ate', - ator: 'ate', - alism: 'al', - iveness: 'ive', - fulness: 'ful', - ousness: 'ous', - aliti: 'al', - iviti: 'ive', - biliti: 'ble', - logi: 'log' - }; - - var step3list = { - icate: 'ic', - ative: '', - alize: 'al', - iciti: 'ic', - ical: 'ic', - ful: '', - ness: '' +if (!Scorer) { + /** + * Simple result scoring code. + */ + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [filename, title, anchor, descr, score] + // and returns the new score. + /* + score: function(result) { + return result[4]; + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: {0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5}, // used to be unimportantResults + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + // query found in terms + term: 5 }; - - var c = "[^aeiou]"; // consonant - var v = "[aeiouy]"; // vowel - var C = c + "[^aeiouy]*"; // consonant sequence - var V = v + "[aeiou]*"; // vowel sequence - - var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 - var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 - var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 - var s_v = "^(" + C + ")?" + v; // vowel in stem - - this.stemWord = function (w) { - var stem; - var suffix; - var firstch; - var origword = w; - - if (w.length < 3) - return w; - - var re; - var re2; - var re3; - var re4; - - firstch = w.substr(0,1); - if (firstch == "y") - w = firstch.toUpperCase() + w.substr(1); - - // Step 1a - re = /^(.+?)(ss|i)es$/; - re2 = /^(.+?)([^s])s$/; - - if (re.test(w)) - w = w.replace(re,"$1$2"); - else if (re2.test(w)) - w = w.replace(re2,"$1$2"); - - // Step 1b - re = /^(.+?)eed$/; - re2 = /^(.+?)(ed|ing)$/; - if (re.test(w)) { - var fp = re.exec(w); - re = new RegExp(mgr0); - if (re.test(fp[1])) { - re = /.$/; - w = w.replace(re,""); - } - } - else if (re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1]; - re2 = new RegExp(s_v); - if (re2.test(stem)) { - w = stem; - re2 = /(at|bl|iz)$/; - re3 = new RegExp("([^aeiouylsz])\\1$"); - re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); - if (re2.test(w)) - w = w + "e"; - else if (re3.test(w)) { - re = /.$/; - w = w.replace(re,""); - } - else if (re4.test(w)) - w = w + "e"; - } - } - - // Step 1c - re = /^(.+?)y$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(s_v); - if (re.test(stem)) - w = stem + "i"; - } - - // Step 2 - re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - suffix = fp[2]; - re = new RegExp(mgr0); - if (re.test(stem)) - w = stem + step2list[suffix]; - } - - // Step 3 - re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - suffix = fp[2]; - re = new RegExp(mgr0); - if (re.test(stem)) - w = stem + step3list[suffix]; - } - - // Step 4 - re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; - re2 = /^(.+?)(s|t)(ion)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(mgr1); - if (re.test(stem)) - w = stem; - } - else if (re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1] + fp[2]; - re2 = new RegExp(mgr1); - if (re2.test(stem)) - w = stem; - } - - // Step 5 - re = /^(.+?)e$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(mgr1); - re2 = new RegExp(meq1); - re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); - if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) - w = stem; - } - re = /ll$/; - re2 = new RegExp(mgr1); - if (re.test(w) && re2.test(w)) { - re = /.$/; - w = w.replace(re,""); - } - - // and turn initial Y back to y - if (firstch == "y") - w = firstch.toLowerCase() + w.substr(1); - return w; - } } - - -/** - * Simple result scoring code. - */ -var Scorer = { - // Implement the following function to further tweak the score for each result - // The function takes a result array [filename, title, anchor, descr, score] - // and returns the new score. - /* - score: function(result) { - return result[4]; - }, - */ - - // query matches the full name of an object - objNameMatch: 11, - // or matches in the last dotted part of the object name - objPartialMatch: 6, - // Additive scores depending on the priority of the object - objPrio: {0: 15, // used to be importantResults - 1: 5, // used to be objectResults - 2: -5}, // used to be unimportantResults - // Used when the priority is not in the mapping. - objPrioDefault: 0, - - // query found in title - title: 15, - // query found in terms - term: 5 -}; - - - - - -var splitChars = (function() { - var result = {}; - var singles = [96, 180, 187, 191, 215, 247, 749, 885, 903, 907, 909, 930, 1014, 1648, - 1748, 1809, 2416, 2473, 2481, 2526, 2601, 2609, 2612, 2615, 2653, 2702, - 2706, 2729, 2737, 2740, 2857, 2865, 2868, 2910, 2928, 2948, 2961, 2971, - 2973, 3085, 3089, 3113, 3124, 3213, 3217, 3241, 3252, 3295, 3341, 3345, - 3369, 3506, 3516, 3633, 3715, 3721, 3736, 3744, 3748, 3750, 3756, 3761, - 3781, 3912, 4239, 4347, 4681, 4695, 4697, 4745, 4785, 4799, 4801, 4823, - 4881, 5760, 5901, 5997, 6313, 7405, 8024, 8026, 8028, 8030, 8117, 8125, - 8133, 8181, 8468, 8485, 8487, 8489, 8494, 8527, 11311, 11359, 11687, 11695, - 11703, 11711, 11719, 11727, 11735, 12448, 12539, 43010, 43014, 43019, 43587, - 43696, 43713, 64286, 64297, 64311, 64317, 64319, 64322, 64325, 65141]; - var i, j, start, end; - for (i = 0; i < singles.length; i++) { - result[singles[i]] = true; - } - var ranges = [[0, 47], [58, 64], [91, 94], [123, 169], [171, 177], [182, 184], [706, 709], - [722, 735], [741, 747], [751, 879], [888, 889], [894, 901], [1154, 1161], - [1318, 1328], [1367, 1368], [1370, 1376], [1416, 1487], [1515, 1519], [1523, 1568], - [1611, 1631], [1642, 1645], [1750, 1764], [1767, 1773], [1789, 1790], [1792, 1807], - [1840, 1868], [1958, 1968], [1970, 1983], [2027, 2035], [2038, 2041], [2043, 2047], - [2070, 2073], [2075, 2083], [2085, 2087], [2089, 2307], [2362, 2364], [2366, 2383], - [2385, 2391], [2402, 2405], [2419, 2424], [2432, 2436], [2445, 2446], [2449, 2450], - [2483, 2485], [2490, 2492], [2494, 2509], [2511, 2523], [2530, 2533], [2546, 2547], - [2554, 2564], [2571, 2574], [2577, 2578], [2618, 2648], [2655, 2661], [2672, 2673], - [2677, 2692], [2746, 2748], [2750, 2767], [2769, 2783], [2786, 2789], [2800, 2820], - [2829, 2830], [2833, 2834], [2874, 2876], [2878, 2907], [2914, 2917], [2930, 2946], - [2955, 2957], [2966, 2968], [2976, 2978], [2981, 2983], [2987, 2989], [3002, 3023], - [3025, 3045], [3059, 3076], [3130, 3132], [3134, 3159], [3162, 3167], [3170, 3173], - [3184, 3191], [3199, 3204], [3258, 3260], [3262, 3293], [3298, 3301], [3312, 3332], - [3386, 3388], [3390, 3423], [3426, 3429], [3446, 3449], [3456, 3460], [3479, 3481], - [3518, 3519], [3527, 3584], [3636, 3647], [3655, 3663], [3674, 3712], [3717, 3718], - [3723, 3724], [3726, 3731], [3752, 3753], [3764, 3772], [3774, 3775], [3783, 3791], - [3802, 3803], [3806, 3839], [3841, 3871], [3892, 3903], [3949, 3975], [3980, 4095], - [4139, 4158], [4170, 4175], [4182, 4185], [4190, 4192], [4194, 4196], [4199, 4205], - [4209, 4212], [4226, 4237], [4250, 4255], [4294, 4303], [4349, 4351], [4686, 4687], - [4702, 4703], [4750, 4751], [4790, 4791], [4806, 4807], [4886, 4887], [4955, 4968], - [4989, 4991], [5008, 5023], [5109, 5120], [5741, 5742], [5787, 5791], [5867, 5869], - [5873, 5887], [5906, 5919], [5938, 5951], [5970, 5983], [6001, 6015], [6068, 6102], - [6104, 6107], [6109, 6111], [6122, 6127], [6138, 6159], [6170, 6175], [6264, 6271], - [6315, 6319], [6390, 6399], [6429, 6469], [6510, 6511], [6517, 6527], [6572, 6592], - [6600, 6607], [6619, 6655], [6679, 6687], [6741, 6783], [6794, 6799], [6810, 6822], - [6824, 6916], [6964, 6980], [6988, 6991], [7002, 7042], [7073, 7085], [7098, 7167], - [7204, 7231], [7242, 7244], [7294, 7400], [7410, 7423], [7616, 7679], [7958, 7959], - [7966, 7967], [8006, 8007], [8014, 8015], [8062, 8063], [8127, 8129], [8141, 8143], - [8148, 8149], [8156, 8159], [8173, 8177], [8189, 8303], [8306, 8307], [8314, 8318], - [8330, 8335], [8341, 8449], [8451, 8454], [8456, 8457], [8470, 8472], [8478, 8483], - [8506, 8507], [8512, 8516], [8522, 8525], [8586, 9311], [9372, 9449], [9472, 10101], - [10132, 11263], [11493, 11498], [11503, 11516], [11518, 11519], [11558, 11567], - [11622, 11630], [11632, 11647], [11671, 11679], [11743, 11822], [11824, 12292], - [12296, 12320], [12330, 12336], [12342, 12343], [12349, 12352], [12439, 12444], - [12544, 12548], [12590, 12592], [12687, 12689], [12694, 12703], [12728, 12783], - [12800, 12831], [12842, 12880], [12896, 12927], [12938, 12976], [12992, 13311], - [19894, 19967], [40908, 40959], [42125, 42191], [42238, 42239], [42509, 42511], - [42540, 42559], [42592, 42593], [42607, 42622], [42648, 42655], [42736, 42774], - [42784, 42785], [42889, 42890], [42893, 43002], [43043, 43055], [43062, 43071], - [43124, 43137], [43188, 43215], [43226, 43249], [43256, 43258], [43260, 43263], - [43302, 43311], [43335, 43359], [43389, 43395], [43443, 43470], [43482, 43519], - [43561, 43583], [43596, 43599], [43610, 43615], [43639, 43641], [43643, 43647], - [43698, 43700], [43703, 43704], [43710, 43711], [43715, 43738], [43742, 43967], - [44003, 44015], [44026, 44031], [55204, 55215], [55239, 55242], [55292, 55295], - [57344, 63743], [64046, 64047], [64110, 64111], [64218, 64255], [64263, 64274], - [64280, 64284], [64434, 64466], [64830, 64847], [64912, 64913], [64968, 65007], - [65020, 65135], [65277, 65295], [65306, 65312], [65339, 65344], [65371, 65381], - [65471, 65473], [65480, 65481], [65488, 65489], [65496, 65497]]; - for (i = 0; i < ranges.length; i++) { - start = ranges[i][0]; - end = ranges[i][1]; - for (j = start; j <= end; j++) { - result[j] = true; - } - } - return result; -})(); - -function splitQuery(query) { - var result = []; - var start = -1; - for (var i = 0; i < query.length; i++) { - if (splitChars[query.charCodeAt(i)]) { - if (start !== -1) { - result.push(query.slice(start, i)); - start = -1; - } - } else if (start === -1) { - start = i; - } - } - if (start !== -1) { - result.push(query.slice(start)); - } - return result; +if (!splitQuery) { + function splitQuery(query) { + return query.split(/\s+/); + } } - - - /** * Search Module */ @@ -417,7 +138,7 @@ var Search = { */ query : function(query) { var i; - var stopwords = ["a","and","are","as","at","be","but","by","for","if","in","into","is","it","near","no","not","of","on","or","such","that","the","their","then","there","these","they","this","to","was","will","with"]; + var stopwords = DOCUMENTATION_OPTIONS.SEARCH_LANGUAGE_STOP_WORDS; // stem the searchterms and add them to the correct list var stemmer = new Stemmer(); @@ -758,4 +479,4 @@ var Search = { $(document).ready(function() { Search.init(); -}); \ No newline at end of file +}); diff --git a/docs/build/html/_static/underscore-1.3.1.js b/docs/build/html/_static/underscore-1.3.1.js old mode 100644 new mode 100755 diff --git a/docs/build/html/_static/underscore.js b/docs/build/html/_static/underscore.js old mode 100644 new mode 100755 diff --git a/docs/build/html/_static/up-pressed.png b/docs/build/html/_static/up-pressed.png old mode 100644 new mode 100755 diff --git a/docs/build/html/_static/up.png b/docs/build/html/_static/up.png old mode 100644 new mode 100755 diff --git a/docs/build/html/_static/websupport.js b/docs/build/html/_static/websupport.js old mode 100644 new mode 100755 diff --git a/docs/build/html/genindex.html b/docs/build/html/genindex.html old mode 100644 new mode 100755 index 7fe4e12..cdc858b --- a/docs/build/html/genindex.html +++ b/docs/build/html/genindex.html @@ -25,8 +25,7 @@ - - + @@ -158,7 +157,7 @@

Index

- © Copyright 2018, Matteo Guadrini <matteo.guadrini@hotmail.it>. + © Copyright 2018, Matteo Guadrini <matteo.guadrini@hotmail.it>

@@ -177,27 +176,17 @@

Index

- - - - + + + + + + + - - - - + - - - + + + + + + + - - - - + - - - - + + + + + + + + - - - - +