#!/usr/bin/env bash # set flag variables to null/default optExcludeBoot=false optExcludeConfidential=false optExcludeLost=true optQuiet=false optUserExclude=() function showHelp() { echo "Usage:" echo "$(basename "$0") [-b -c -l -q] [-s || -t ] [-e ...] [-i ...] [-- [rsync-opts]]" echo "Position Arguments:" echo " destination path to copy system files to" echo " [rsync-opts] additional options to pass to rsync command" echo echo "Options:" echo " -b excludes boot directory" echo " -c excludes some confidential files (currently only .bash_history and connman network lists)" echo " -l includes lost+found directory" echo " -q activates quiet mode (no confirmation)" echo " -s makes copy of current system" echo " -t makes copy of system located at the " echo " -e an additional exclude directory (one dir one -e, do not use it with *)" echo " -i an additional include. This has higher precedence than -e, -t, and -s" echo " -h display this help message." if [[ "$1" -ge 0 ]]; then exit "$1" else exit 0 fi } function errorMsg() { local rc=0 if [[ "$1" -gt 0 ]]; then rc="$1" shift fi echo "$*" >&2 if [[ "$rc" -gt 0 ]]; then exit "$rc" fi } # reads options: while [[ $# -gt 0 ]]; do while getopts ":t:e:i:sqcblh" flag; do case "$flag" in t) sourcePath="$OPTARG";; s) sourcePath="/";; q) optQuiet=true;; c) optExcludeConfidential=true;; b) optExcludeBoot=true;; l) optExcludeLost=false;; e) optUserExclude+=("$OPTARG");; i) optUserInclude+=("$OPTARG");; h) showHelp 0;; \?) errorMsg 1 "Invalid option: -$OPTARG";; :) errorMsg 1 "Option -$OPTARG requires an argument.";; esac done || exit 1 [[ $OPTIND -gt $# ]] && break # reached the end of parameters shift $((OPTIND - 1)) # Free processed options so far OPTIND=1 # we must reset OPTIND if [[ -z "$targetPath" ]]; then targetPath=$1 else rsyncArgs[${#rsyncArgs[*]}]=$1 fi #args[${#args[*]}]=$1 # save first non-option argument (a.k.a. positional argument) shift # remove saved arg done # checks if run as root: if [[ "$(id -u)" -ne 0 ]]; then echo "$(basename "$0"): must run as root" exit 250 fi if [[ -z "$sourcePath" ]]; then echo "$(basename "$0"): no source path specified" exit 1 elif [[ ! -d "$sourcePath" ]]; then echo "$(basename "$0"): no source path does not exist" exit 1 elif [[ "$sourcePath" != */ ]]; then # make sure source path ends with slash sourcePath="${sourcePath}/" fi if [[ -z "$targetPath" ]]; then echo "$(basename "$0"): no destination path specified" exit 1 elif [[ ! -d "$targetPath" ]]; then echo "$(basename "$0"): destination path (\`$targetPath\`) does not exist" exit 1 elif [[ "$targetPath" != */ ]]; then # make sure destination path ends with slash targetPath="${targetPath}/" fi # Excludes: syncExcludes=( "dev/*" "var/tmp/*" "media/*" "mnt/*/*" "proc/*" "run/*" "sys/*" "tmp/*" "var/lock/*" "var/log/*" "var/run/*" "var/lib/docker/*" "var/lib/containers/*" "var/lib/machines/*" "var/lib/libvirt/*" "var/lib/lxd/*" "home/*/*" ) #EXCLUDES_DEFAULT_PORTAGE=( syncExcludesPortage=( "var/db/repos/*/*" "var/cache/distfiles/*" "var/cache/binpkgs/*" "usr/portage/*" ) syncIncludes=( "dev/console" "dev/null" "var/db/pkg/*" ) syncExcludes=("${syncExcludes[@]/#/"$sourcePath"}") syncIncludes=("${syncIncludes[@]/#/"$sourcePath"}") if [[ "$sourcePath" == '/' ]]; then if command -v portageq &>/dev/null; then portageRepos=$(portageq get_repos /) for i in ${portageRepos}; do repoPath=$(portageq get_repo_path / "${i}") syncExcludes+=("${repoPath}/*") done syncExcludes+=("$(portageq distdir)/*") syncExcludes+=("$(portageq pkgdir)/*") else syncExcludes+=("${syncExcludesPortage[@]/#/"/"}") fi else syncExcludes+=("${syncExcludesPortage[@]/#/"$sourcePath"}") fi if $optExcludeConfidential; then syncExcludes+=("${sourcePath}home/*/.bash_history") syncExcludes+=("${sourcePath}root/.bash_history") syncExcludes+=("${sourcePath}var/lib/connman/*") fi if $optExcludeBoot; then syncExcludes+=("${sourcePath}boot/*") fi if $optExcludeLost; then syncExcludes+=("lost+found") fi syncExcludes+=("${optUserExclude[@]}") syncIncludes+=("${optUserInclude[@]}") # Generic rsync options: rsyncOptions=( -avxHAXS --numeric-ids "--info=progress2" ) rsyncOptions+=("${rsyncArgs[@]}") # if not in quiet mode, this message will be displayed: if ! $optQuiet; then echo "Are you sure that you want to copy system files located under" echo "$sourcePath" echo "to the following directory" echo "$targetPath" echo echo "WARNING: since all data is copied by default the user should exclude all" echo "security- or privacy-related files and directories, which are not" echo "already excluded, manually per cmdline." echo "example: \$ $(basename "$0") -s -e \"/etc/ssh/ssh_host*\" " echo echo "COMMAND LINE PREVIEW:" echo 'rsync' "${rsyncOptions[@]}" "${syncIncludes[@]/#/--include=}" "${syncExcludes[@]/#/--exclude=}" "$sourcePath" "$targetPath" echo echo -n 'Type "yes" to continue or anything else to quit: ' read -r promptAgree if [[ "${promptAgree,,}" == "yes" ]]; then optQuiet=true fi fi # start stage4 creation: if $optQuiet; then #echo "Would've worked" rsync "${rsyncOptions[@]}" "${syncExcludes[@]/#/--exclude=}" "$sourcePath" "$targetPath" fi