## Some default values of important variables ## Settings GPG_RECIPIENT="robin.oburka@nic.cz" ## Style TIME_FMT="\nBackup took:\t%E" ## Internal variables - backup process NAME="" REMOTE="" GENERATE="" COMMAND="" PLACES="" EXCLUDE="" STORE="" AFTER="" ## Pipeline variables PID_GENERATE="" PID_STORE="" PID_FILE="pids" STAGE="0" ## Internal variables - program logic PERSN_FILE_NAME=".backuper.sh.per" ## Output helpers title() { TXT="$1" printf "\n%s\n==========================================\n" "$TXT" } get_size() { FILE="$1" du -h "$FILE" | awk '{ print $1 }' } ## Getting data helpers get_home() { CURRENT_USER="$(id --real --user --name)" CU_HOME="$(getent passwd "$CURRENT_USER" | cut -d: -f6)" echo "$CU_HOME" } result_file() { if [ -n "$DST" ]; then echo "$DST"/"$FILENAME_ENC" else echo "$FILENAME_ENC" fi } ## Program control error() { echo "ERROR: $1" >&2 exit 1 } die() { if [ -n "$1" ]; then echo "DIE: $1" >&2 fi ## -6 ~ SIGABRT ## This is the only SIGNAL that cleanup doesn't reset kill -6 "$$" } ## Interface functions name() { NAME="$1" compute_variables } remote() { REMOTE="$1" } generate() { GENERATE="$1" } cmd() { COMMAND="$1" } places() { PLACES="$1" [ "$#" -gt 1 ] && error "Specify places as string" } exclude() { STR="" for EXC in "$@"; do STR="$STR --exclude=$EXC" done EXCLUDE="$STR" } store() { STORE="$1" } after() { AFTER="$1" } run() { title "Run command" echo "$@" echo $@ | $REMOTE sh -s printf "\n" } store_file() { [ -z "$1" ] && error "Specify destination" store "sh -c 'cat > ${1}/${FILENAME_ENC}'" } ## Internal functions test_input() { [ -z "$NAME" ] && error "Specify backup name" [ -z "$COMMAND" -a -z "$PLACES" ] && error "Specify backup subject" [ -n "$COMMAND" -a -n "$PLACES" ] && error "Do not specify two types of backup at once" [ -z "$STORE" ] && error "Specify store command" } compute_variables() { UTS="$(date +"%s")" TS="$(date +"%Y-%m-%d_%H-%M-%S")" FILENAME="backup_"$NAME"_"$TS".tar" FILENAME_ENC="${FILENAME}.bz2.gpg" RUNDIR="/tmp/backuper_run_dir_$$_$UTS" } prepare() { trap 'error_handler' EXIT INT QUIT TERM ABRT HUP ILL TRAP BUS FPE SEGV mkdir -p "$RUNDIR" } error_handler() { cleanup exit 42 } cleanup() { trap - EXIT INT QUIT TERM HUP ILL TRAP BUS FPE SEGV for PID in $(cat "$RUNDIR"/"$PID_FILE"); do if ps --ppid $$ | grep -q "$PID"; then kill "$PID" fi done [ -n "$PID_GENERATE" ] && if ps --ppid $$ | grep -q "$PID_GENERATE"; then kill "$PID_GENERATE" fi [ -n "$PID_STORE" ] && if ps --ppid $$ | grep -q "$PID_STORE"; then kill "$PID_STORE" fi if [ -d "$RUNDIR" ]; then rm -rf "$RUNDIR" fi } backup_done() { if [ -n "$AFTER" ]; then title "After backup hook" echo "$AFTER" | sh -s || die "After-backup hook failed" fi } personalization() { HOME="$(get_home)" [ -f "$HOME"/"$PERSN_FILE_NAME" ] && . "$HOME"/"$PERSN_FILE_NAME" } add_stage() { ## Prepare pipes NEXTSTAGE="$((STAGE+1))" [ ! -p "$RUNDIR"/fifo_"$STAGE" ] && mkfifo "$RUNDIR"/fifo_"$STAGE" [ ! -p "$RUNDIR"/fifo_"$NEXTSTAGE" ] && mkfifo "$RUNDIR"/fifo_"$NEXTSTAGE" ## Run requested command and store PID $@ > "$RUNDIR"/fifo_"$NEXTSTAGE" < "$RUNDIR"/fifo_"$STAGE" || die & echo "$!" >> "$RUNDIR"/"$PID_FILE" ## Process started - increment STAGE="$NEXTSTAGE" } start_store() { STORE="$STORE < "$RUNDIR"/fifo_"$NEXTSTAGE"" echo "$STORE" | sh -s } start_command() { title "Create backup" if [ -n "$COMMAND" ]; then echo "$COMMAND" | /usr/bin/time -f "$TIME_FMT" $REMOTE $GENERATE sh -s > "$RUNDIR"/fifo_0 else ## Exclude keep without quotes /usr/bin/time -f "$TIME_FMT" $REMOTE $GENERATE tar c --preserve-permissions $EXCLUDE "$PLACES" > "$RUNDIR"/fifo_0 fi } start_pipeline() { add_stage "mbuffer -q -m 100M" add_stage "pbzip2" add_stage "mbuffer -q -m 100M" add_stage "gpg -z0 -r robin.oburka@nic.cz -e" ## OK, this is strange. If store command fail, mbuffer forks itself with ## PID that I'm not able to get (event from list of children). ## ... and it takes 100% CPU. So, for now: Do not use mbuffer as the last one. #add_stage "mbuffer -q -m 100M" start_store || die & PID_STORE=$! start_command || die & PID_GENERATE=$! wait "$PID_STORE" "$PID_GENERATE" for PID in $(cat "$RUNDIR"/"$PID_FILE"); do wait "$PID" done } backup() { test_input prepare personalization start_pipeline backup_done cleanup }