#!/bin/bash
#  sys/backup - System Backup/Restore Support

set -e

lib_load 'net/tool/rsync'
lib_load 'sys/tool/tar'


######
# Library Configuration

sys_backup_config_init() {
	# $backup_modes[] - List of all supported modes of operation.
	# Default: ``backup restore``
	lib_setting_arrays -ro backup_modes
	backup_modes=( backup restore )

	# $backup_targets_supported[] - List of targets supported by all
	# operation modes.
	# Default: ``full config data``
	lib_setting_arrays -ro backup_targets_supported
	backup_targets_supported=( full config data )

	# $backup_targets[] - List of backup/restore targets to execute.
	lib_setting_arrays backup_targets

	# $backup_datadir - Location of all package backup data
	# Default: ``$backup_datadir/backups``
	lib_setting_vars -ro backup_datadir
	backup_datadir="$datadir/backups"

	# $backup_stagedir - Path to staged backup data.
	# Default: ``$backup_datadir/stage``
	lib_setting_vars -ro backup_stagedir
	backup_stagedir="$backup_datadir/stage"

	# backup_script_stagedir - Path to staged backup data for the
	# current script.
	# Default: ``$backup_stagedir/$script_name``
	lib_setting_vars -ro backup_script_stagedir
	backup_script_stagedir="$backup_stagedir/$script_name"

	# $backup_storedir - Path to stored backup archives.
	# Default: ``$backup_datadir/store``
	lib_setting_vars -ro backup_storedir
	backup_storedir="$backup_datadir/store"

	# $backup_rsync_opts[] - Extra options to pass to ``rsync``.
	# Default: ``--archive``
	lib_setting_arrays -ro backup_rsync_opts
	backup_rsync_opts=( --archive )
}


######
# Backup Targets

# backup_target_add() - Adds a backup target to ``$backup_targets[]``.
backup_target_add() {
	local target=$1
	if in_list backup_targets $target; then
		warn "$target: ignoring duplicate target"
	else
		backup_targets+=( "$target" )
	fi
}


######
# File Backups

# backup_file() - Uses ``rsync`` to back up a single file or directory ($1)
# to the staging directory.
backup_file() {
	has_args 1 "$@"
	local src=$1
	[ -f "$src" ] || error "$src: does not exist"
	local dst="${backup_script_stagedir}${src}"
	file_mkdir "$dst"
	rsync_run "${backup_rsync_opts[@]}" "$src" "$dst"
}

# restore_file() - Uses ``rsync`` to restore a single file or directory
# ($1) from the staging directory.
restore_file() {
	local dst=$1
	local src="${backup_script_stagedir}${dst}"
	[ ! -f "$dst" ] || warn "$dst: exists, overwriting..."
	file_mkdir "$src"
	rsync_run "${backup_rsync_opts[@]}" "$src" "$dst"
}

# backup_exec() - Executes a command for all ``$backup_targets[]`` for
# the given backup mode ($1).  Additional arguments ($@) are passed
# to the backup function.
backup_exec() {
	min_args 1 "$@"
	local mode=$1
	shift

	debug "$mode: starting..."
	for target in "${backup_targets[@]}"; do
		local cmd="${mode}_${target}"
		if script_cmd_exists $cmd; then
			debug "$mode $target: starting ..."
			script_cmd_exec $cmd "$@"
			debug "$mode $target: ... done"
		elif lib_cmd_exists backup $cmd; then
			debug "$mode $target: built-in: starting ..."
			lib_cmd_exec backup $cmd "$@"
			debug "$mode $target: built-in: ... done"
		else
			debug "$mode $target: not supported"
		fi
	done
	debug "$mode: ... done!"
}


######
# Backup archives

backup_pack() {
	min_args 2 "$@"
	local archive=$1
	[ ! -f "$archive" ] || error "$archive: archive exists"
	shift

	file_mkdir "$archive"
	local tardir="$backup_stagedir"
	tar_create "$archive" "$@"
}

backup_unpack() {
	has_args 1 "$@"
	local file=$1
	[ -f "$file" ] || error "$file: archive does not exist"

	file_mkdir "$archive"
	local tardir="$backup_stagedir"
	tar_extract "$file"
}


######
# Built-in backup methods

backup_config_file() {
	local src=$1
	if [ -f "$src" ]; then
		${backup_mode}_file "$src"
	else
		# local/user configuration files may not exist
		debug "$src: does not exist; skipping..."
	fi
}

backup_config() {
	for_each backup_config_file "${sys_configs[@]}"
}

backup_backup_config() { local backup_mode=backup; backup_config "$@"; }
backup_restore_config() { local backup_mode=restore; backup_config "$@"; }


######
# Tool backup/restore

backup_tool_tarfile() { echo "$backup_stagedir/$1.tar"; }

backup_tool() {
	local tool=$1
	shift

	run $tool backup "$@"

	local archive
	archive=$(backup_tool_tarfile "$tool")
	run rm -f "$archive"
	backup_pack "$archive" $tool
}

restore_tool() {
	local tool=$1
	shift
	local archive
	archive=$(backup_tool_tarfile "$tool")

	backup_unpack "$archive"

	run "$tool" restore "$@"
}

######
# Host backup/restore interface

# backup_host_storedir - Path to stored backup data for host ($1).
backup_host_storedir() { echo "$backup_storedir/$1"; }

backup_host() {
	has_args 2 "$@"
	local name=$1
	local target=$2

	info "host backup: starting ..."

	# secure archive name before starting
	local aname
	aname="$target-$(date +%y%m%d-%H%M%S).tar.xz"

	for_each_tool _backup_host_tool $target

	local files=( "${package_apps[@]}" )
	list_map tar_filename files

	local archive
	archive="$(backup_host_storedir "$name")/$aname"
	backup_pack "$archive" "${files[@]}"

	info "host backup: ... done."
}

_backup_host_tool() {
	local target=$1
	local tool=$2
	debug "host $name: $tool: backing up $archive"
	backup_tool "$tool" "$target"
}

restore_host() {
	has_args 2 "$@"
	local name=$1
	local archive=$2
	debug "host $name: unpacking $archive"
	backup_unpack "$archive"

	for_each_tool _restore_host_tool
}
_restore_host_tool() {
	local tool=$1

	local archive
	archive=$(backup_tool_tarfile "$tool")

	debug "host $name: $tool: restoring $archive"
	restore_tool "$tool" "$archive"
}

######
# Remote Backup and Restore

backup_remote() {
	local host=$1
	local target=$2
	warn "$FUNCNAME: FIXME: $target backup unimplemented ($host)"
}

restore_remote() {
	local host=$1
	local target=$2
	warn "$FUNCNAME: FIXME: $target restore unimplemented ($host)"
}

######
# Cloud Backup/Restore Interface

backup_cloud() {
	has_args 2 "$@"
	local cloud=$1
	local target=$2
	for_each_cloud_host "$cloud" backup_cloud_host "$cloud" "$target"
}

backup_cloud_host() {
	local cloud=$1
	local target=$2
	local name=$3
	info "$cloud: $name: cloud host $target backup: starting..."
	if [ "$name" = "$host" ]; then
		backup_host "$name" "$target"
	else
		backup_remote "$name" "$target"
	fi
	info "$cloud: $name: cloud host $target backup: ... done!"
}

restore_cloud() {
	has_args 2 "$@"
	local cloud=$1
	local target=$2
	for_each_cloud_host "$cloud" restore_cloud_host "$cloud" "$target"
}

restore_cloud_host() {
	local cloud=$1
	local target=$2
	local name=$3
	info "$cloud: $name: cloud host $target restore: starting..."
	if [ "$name" = "$host" ]; then
		restore_host "$name" "$target"
	else
		restore_remote "$name" "$target"
	fi
	info "$cloud: $name: cloud host $target restore: ... done!"
}


######
# Backup CLI support

backup_dispatch() {
	min_args 2 "$@"
	local mode=$1
	shift

	local -a backup_targets

	local got_opt=true
	while $got_opt; do
		local target=$1
		case "$target" in
		(data|config)
			backup_target_add "$target"
			shift
			;;
		(full)
			backup_target_add config
			backup_target_add data
			shift
			;;
		('')
			got_opt=false
			;;
		(*)
			error_usage "$target: unknown target"
			;;
		esac
	done

	[ "${backup_targets[*]}" ] || error "$mode: no targets specified"

	backup_exec "$mode" "$@"
}

backup_usage() {
	cat <<USAGE
<cmd>
Backup Commands:
	full				Saves all script-related files
	config				Saves script configuration files
	data				Saves script data files

Archive Commands
	pack <file>			Pack staging directory into archive
USAGE
}

restore_usage() {
	cat <<USAGE
<cmd> <file>
Restore Commands:
	full <file>			Perform a full restore
	config <file>			Restore script configuration files
	data <file>			Restore script data files

Archive Commands
	unpack <file>			Unpack archive to staging directory
USAGE
}

View the Developer Guide Index

View the Reference Manual Index


Generated on Fri Jul 28 14:35:57 PDT 2017 by mcsh d14 v0.23.0.