Skip to content

Instantly share code, notes, and snippets.

@sneppy
Created March 10, 2019 20:35
Show Gist options
  • Save sneppy/4eda6111d3825f346d5b4b1d3f2472b4 to your computer and use it in GitHub Desktop.
Save sneppy/4eda6111d3825f346d5b4b1d3f2472b4 to your computer and use it in GitHub Desktop.
An extension of the bitpocket by sickill -> https://github.com/sickill/bitpocket
#!/bin/bash
LANG=$(locale | grep LANG= | sed 's:LANG=::')
if [[ -z "$LANG" ]]; then
LANG="C"
fi
export LC_ALL=$LANG # for stable "sort" output
# Paths
DOT_DIR=.bitpocket
CFG_FILE="$DOT_DIR/config"
TMP_DIR="$DOT_DIR/tmp"
STATE_DIR="$DOT_DIR/state"
LOCK_DIR="$TMP_DIR/lock" # Use a lock directory for atomic locks. See the Bash FAQ http://mywiki.wooledge.org/BashFAQ/045
# Default settings
SLOW_SYNC_TIME=10
SLOW_SYNC_FILE="$TMP_DIR/slow"
RSYNC_RSH="ssh"
REMOTE_BACKUPS=false
BACKUPS=true
LOCAL_MOUNTPOINT=false
REMOTE_MOUNTPOINT=false
# Default command-line options and such
COMMANDS=()
ARGS=()
OPTIONS=()
# Load config file
[[ -f "$CFG_FILE" ]] && . "$CFG_FILE"
# Colors
GREEN=""
RED=""
CLEAR=""
YELLOW=""
if [[ -t 1 ]]; then
GREEN="\x1b\x5b1;32m"
RED="\x1b\x5b1;31m"
YELLOW="\x1b\x5b1;33m"
CLEAR="\x1b\x5b0m"
fi
# Test for GNU versions of core utils. Bail if non-GNU.
if sed --version >/dev/null 2>/dev/null; then
alias cp="cp --parents --reflink=auto"
else
echo "\
Warning: ---------------------------------------------------
It seems like you are running on a system without GNU coreutils. bitpocket
may not work correctly on this platform. Please beware and report any issues
you encounter.
"
alias sed="sed -E"
fi
# Decide on runner (ssh / bash -c)
function setup_remote() {
if [[ -n "$REMOTE_HOST" ]]; then
REMOTE_RUNNER="$RSYNC_RSH $REMOTE_HOST"
REMOTE="$REMOTE_HOST:$REMOTE_PATH"
else
REMOTE_RUNNER="bash -c"
REMOTE="$REMOTE_PATH"
fi
}
setup_remote
# Version of the state files. Original version is 1, second version with
# leading mode is 2.
#
# Current state version is 2, and the format of the state file is
# ":" TYPE MODE "/" PATH "\n"
# Where TYPE = ("-" | "d" | "l" )
# MODE = unix mode (9-character) (like "rw-rw-r--")
# PATH = relative full-path of file
STATE_VERSION=2
REMOTE_TMP_DIR="$REMOTE_PATH/$DOT_DIR/tmp"
HOSTNAME="$(hostname)"
# Temp files used, deleted at the end of the script
tmp_bitignore_files=""
# Don't sync user ignored files
shopt -s globstar
for bitignore in **/.bitignore; do
# Check that file actually exists
if [[ -f $bitignore ]]; then
# Prepend relative dir
tmp_bitignore=$(mktemp --tmpdir=$TMP_DIR)
rel_dir=$(dirname $bitignore)
if [ $rel_dir == "." ]
# Prepending current folder '.' causes problem
# thus in this case, don't prepend anything
# just copy
then cp $bitignore $tmp_bitignore
else sed -e "s:^:$rel_dir/:" $bitignore > $tmp_bitignore
fi
# Add to list of temp bitignore files
tmp_bitignore_files="$tmp_bitignore_files $tmp_bitignore"
# Add to exclude file list
user_exclude="$user_exclude --exclude-from $tmp_bitignore"
fi
done
shopt -u globstar
USER_RULES="$user_exclude"
TIMESTAMP=$(date "+%Y-%m-%d.%H%M%S")
export RSYNC_RSH
function prefix() {
while read -r line; do
echo "$1$line"
done
}
function init {
if [[ -d "$DOT_DIR" || -f "$CFG_FILE" ]]; then
echo "fatal: Current directory already initialized for bitpocket"
exit 128
fi
if [[ $# == 2 ]]; then
REMOTE_HOST=$1
shift
fi
REMOTE_PATH="$1"
setup_remote
if $REMOTE_RUNNER "[ ! -d '$1' ]"; then
echo "fatal: '$REMOTE': Remote path is not accessible"
exit 128
fi
mkdir "$DOT_DIR"
cat <<EOF > "$CFG_FILE"
## Host and path of central storage
REMOTE_HOST=$REMOTE_HOST
REMOTE_PATH="$REMOTE_PATH"
## Backups -----------------------------------
## Enable file revisioning locally in the pull phase (>false< to disable)
BACKUPS=true
## Make revisions of files on the REMOTE_HOST in the push phase.
REMOTE_BACKUPS=false
## Rsync Advanced Options --------------------
## SSH command with options for connecting to \$REMOTE
# RSYNC_RSH="ssh -p 22 -i $DOT_DIR/id_rsa"
## Uncomment following line to follow symlinks (transform it into referent file/dir)
# RSYNC_OPTS="-L"
## Use the following if a remote FAT or VFAT filesystem is being synchronized.
## This is automatically detected for local FAT filesystems.
# RSYNC_OPTS="--no-perms --no-owner --no-group --modify-window=2"
## Uncomment following lines to get sync notifications
# SLOW_SYNC_TIME=10
# SLOW_SYNC_START_CMD="notify-send 'BitPocket sync in progress...'"
# SLOW_SYNC_STOP_CMD="notify-send 'BitPocket sync finished'"
## Indicate a remote mount point. If this is set and the mountpoint is not
## mounted, then the bitpocket sync will abort. This addresses situations where
## a sync target appears empty because it is not mounted. Such a sync might
## result in all local or remote data disappearing. Give the expected
## mountpoint of the local and/or remote target.
# REMOTE_MOUNTPOINT=/
EOF
echo "Initialized bitpocket directory at $(pwd)"
echo "Please have a look at the config file ($DOT_DIR/config)"
}
function log {
assert_dotdir
tail -f "$DOT_DIR/log"
}
function prefix() {
while read -r line
do
echo "$1$line"
done
}
function pull() {
# Actual fetch
# Pulling changes from server
# Order of includes/excludes/filters is EXTREMELY important
echo
echo "# Pulling changes from server"
local BACKUP_TARGET="$DOT_DIR/backups/$TIMESTAMP"
local DO_BACKUP=""
if [[ $BACKUPS == true ]]
then
echo "# >> Saving current state and backing up files (if needed)"
local DO_BACKUP="--backup --backup-dir=$BACKUP_TARGET"
fi
cp "$STATE_DIR/tree-current" "$TMP_DIR/tree-after"
# Determine what will be fetched from server and make backup copies of any
# local files to be deleted or overwritten.
#
# Only delete locally if deleted remotely. To do this, use the remote-del
# file to set the *R*isk filter flag (allow delete), and protect everything
# else with the *P*rotect flag.
#
# Order of includes/excludes/filters is EXTREMELY important
#
# TODO: Consider adding %U and %G to the output format to capture owner and
# group changes
prefix "R " < "$TMP_DIR/remote-del" \
| rsync -auzx --delete --exclude "/$DOT_DIR" \
--exclude-from="$TMP_DIR/local-del" \
--exclude-from="$TMP_DIR/local-add-change" \
--filter=". -" \
--filter="P **" \
$DO_BACKUP \
--out-format=%i:%B:%n \
$RSYNC_OPTS $USER_RULES $REMOTE/ . \
| detect_changes \
| prefix " | " || die "PULL"
# Some versions of rsync will create the backup dir, even if it doesn't get
# populated with any backups
if [[ -d "$BACKUP_TARGET" ]]
then
if (shopt -s nullglob dotglob; f=("$BACKUP_TARGET"/*); ((${#f[@]})))
then
echo " | Some files were backed up to $BACKUP_TARGET"
else
rmdir "$BACKUP_TARGET"
fi
fi
}
function detect_changes() {
# Create a duplicate of STDOUT for logging of backed-up files, and use fd#4
# for logging of deleted files, which need to be sorted
exec 3> >(grep -Ff /dev/stdin "$STATE_DIR/tree-current" | sort > "$TMP_DIR/pull-delete")
while read -r line
do
IFS=":" read -ra info <<< "$line"
operation=${info[0]}
filename="${info[*]:2}"
if [[ "$operation" =~ ^\*deleting ]]
then
echo "/${filename}" >&3
elif [[ "$operation" =~ \+\+\+\+$ ]]
then
# Mark as added locally (with proper mode)
mode=${info[1]}
filetype="${operation:1:1}"
filetype="${filetype/f/-}"
echo ":${filetype}${mode}/$filename" >> "$TMP_DIR/tree-after"
fi
echo "$operation $filename"
done
exec 3>&-
}
function push() {
# Actual push
# Send new and updated, remotely remove files deleted locally
# Order of includes/excludes/filters is EXTREMELY important
echo
echo "# Pushing changes to server"
local BACKUP_TARGET="$DOT_DIR/backups/$TIMESTAMP"
local DO_BACKUP=""
if [[ $REMOTE_BACKUPS == true ]]
then
echo "# >> Saving current state and backing up files (if needed)"
DO_BACKUP="--backup --backup-dir=$BACKUP_TARGET"
fi
# Do not push back remotely deleted files
prefix "R " < "$TMP_DIR/local-del" \
| rsync -auzxi --delete $RSYNC_OPTS --exclude "/$DOT_DIR" \
--exclude-from="$TMP_DIR/remote-del" \
--filter=". -" \
--filter="P **" \
$DO_BACKUP \
$USER_RULES . $REMOTE/ \
| prefix " | " || die "PUSH"
# Some versions of rsync will create the backup dir, even if it doesn't get
# populated with any backups
if [[ $REMOTE_BACKUPS == true ]]
then
$REMOTE_RUNNER "
cd '$REMOTE_PATH'
if [[ -d '$BACKUP_TARGET' ]]
then
if (shopt -s nullglob dotglob; f=('$BACKUP_TARGET'/*); ((\${#f[@]})))
then
echo ' | Some files were backed up to $BACKUP_TARGET'
else
rmdir '$BACKUP_TARGET'
fi
fi
"
fi
}
function scrub_rsync_list {
# Capture the 1st and 5th columns (mode and file name), remove blank lines,
# drop the `/.` folder/file, and escape files with `[*?` characters in
# them. Use the ASCII "file separator" (0x1c) to separate the filename from
# the file mode in the output.
sed -En '/^[dl-]/ {
s:^([^[:space:]]*)[[:space:]]*[^[:space:]]*[[:space:]]*[^[:space:]]*[[:space:]]*[^[:space:]]*[[:space:]]*(.*$):\:\1/\2:
/\/\.$/ b
s:([*?[]):\\\1:g
p
}'
}
function analyse {
# Check what has changed
touch "$STATE_DIR/tree-prev"
# Save before-sync state
# Must be done with rsync itself (rather than find) to respect includes/excludes
# Order of includes/excludes/filters is EXTREMELY important
echo "# Capturing current local and remote state"
echo " | Root dir: $(pwd)"
# Collect the current snapshot of the remote tree, if a previous tree
# snapshot is available locally
if [[ -s "$STATE_DIR/tree-prev" ]]; then
echo " | Root dir: $REMOTE"
rsync --list-only --recursive --exclude "/$DOT_DIR" $USER_RULES $REMOTE/ \
| scrub_rsync_list \
| sort -k 1.12 \
> "$STATE_DIR/remote-tree-current" &
local remote_tree_pid=$!
fi
# Collect the current snapshot of the local tree
rsync --list-only --recursive --exclude "/$DOT_DIR" $USER_RULES . \
| scrub_rsync_list \
| sort -k 1.12 \
> "$STATE_DIR/tree-current" \
|| die "SNAPSHOT"
# Prevent bringing back locally deleted files
if [[ -s "$STATE_DIR/tree-prev" ]]
then
# Compile a list of files added locally and removed locally. These
# should be protected in the pull phase. Escape rsync filter wildcard
# characters, remove blank lines
strip_mode < "$STATE_DIR/tree-prev" \
| comm -23 - <(strip_mode < "$STATE_DIR/tree-current") \
> "$TMP_DIR/local-del"
# Honor local mode changes (link to file, as well as permissions).
# These should be protected in the pull phase. Ignore files already
# masked as locally added or locally removed.
if [[ $STATE_VERSION -gt 1 ]]
then
# Use comm to detect the differences in the pseudo-sorted files
# (they're sorted on column 12). Comm will detect some false
# positives, so run the output back through `grep` to check if there
# is an exact match in the previous state file. This is much faster
# than resorting both of the files if they are somewhat large.
comm -23 "$STATE_DIR/tree-current" "$STATE_DIR/tree-prev" 2>/dev/null \
| while read -r line
do
if ! grep -Fxq "$line" "$STATE_DIR/tree-prev"
then
echo "$line"
fi
done \
| strip_mode \
> "$TMP_DIR/local-add-change"
else
# In transition to the new state file, ignore the changes in mode
comm -23 <(strip_mode < "$STATE_DIR/tree-current") "$STATE_DIR/tree-prev" \
> "$TMP_DIR/local-add-change"
fi
# Also protect the folders where files were locally added and removed so
# that the modify times of them are not reverted to the remote ones
cat "$TMP_DIR/local-del" "$TMP_DIR/local-add-change" \
| while read -r line; do
# Only parent folders of files--not folders
if [[ "${line: -1}" != "/" ]]
then
echo "${line%/*}/"
fi
done \
| sort -u \
>> "$TMP_DIR/local-add-change"
# Prevent deleting local files which were not deleted remotely ie.
# prevent deleting newly added local files. Compile a list of remotely
# deleted files which should be protected in the push phase.
wait $remote_tree_pid
strip_mode < "$STATE_DIR/tree-prev" \
| comm -23 - <(strip_mode < "$STATE_DIR/remote-tree-current") \
> "$TMP_DIR/remote-del"
else
# In the case of a new sync, where no previous tree snapshot is available,
# assume all the files on the local side should be protected
cp "$STATE_DIR/tree-current" "$TMP_DIR/local-add-change"
touch "$TMP_DIR/local-del"
touch "$TMP_DIR/remote-del"
fi
}
function strip_mode {
if [[ $STATE_VERSION -gt 1 ]]; then
cut -c12-
else
# State file version might be intermixed. Evaluate each line
sed -E "s/^:.{10}//"
fi
}
# Do the actual synchronization
function sync {
assert_dotdir
assert_mountpoints
acquire_lock
acquire_remote_lock
check_state_version
detect_fatfs
echo
echo -e "${GREEN}bitpocket started${CLEAR} at $(date)."
echo
# Fire off slow sync start notifier in background
on_slow_sync_start
# Build addtion/deletion lists
analyse
if [[ "${OPTIONS[*]}" =~ pretend ]]; then
RSYNC_OPTS="${RSYNC_OPTS} --dry-run"
echo -e "${YELLOW}Pretending to sync only. No changes will be made${CLEAR}"
fi
pull
push
if [[ ! "${OPTIONS[*]}" =~ pretend ]]; then
# Save after-sync state
# Generate a incremental snapshot of the local tree including files deleted
# and added via the pull()
#
# Remove pull-deleted files from the tree-after snapshot
sort -k1.12 "$TMP_DIR/tree-after" \
| comm -23 - "$TMP_DIR/pull-delete" 2> /dev/null \
| sed -e "s:/\$::" \
> "$STATE_DIR/tree-prev"
fi
rm "$TMP_DIR/tree-after"
# Fire off slow sync stop notifier in background
on_slow_sync_stop
cleanup
echo
echo -e "${GREEN}bitpocket finished${CLEAR} at $(date)."
echo
}
# Pack backups into a git repository
function pack {
assert_dotdir
# Git is required for backup packing
if ! builtin type -p git > /dev/null; then
echo "fatal: For backup packing, git must be installed"
exit 128
fi
# If pack directory is missing, create it and prepare git repo
if [ ! -d "$DOT_DIR/pack" ]
then
mkdir $DOT_DIR/pack
git init $DOT_DIR/pack
touch $DOT_DIR/pack/.git-init-marker
(cd $DOT_DIR/pack && git add .)
(cd $DOT_DIR/pack && git commit -a -q -m "INIT")
fi
# If any backups exist, pack them into the repo
if [ -d "$DOT_DIR/backups" ] && [ "$(ls -A $DOT_DIR/backups)" ]
then
for DIR in $DOT_DIR/backups/*
do
TSTAMP=$(echo $DIR | sed "s|.*/||")
if [ "$(ls -A $DIR)" ]
then
echo -n "Processing: $TSTAMP ... "
echo -n "Moving ... "
(cp -rfl $DIR/* $DOT_DIR/pack && rm -rf $DIR) || die MV
echo -n "Adding ... "
(cd $DOT_DIR/pack && git add .) || die ADD
echo -n "Committing ... "
# Commit only if repository has uncommitted changes
(cd $DOT_DIR/pack \
&& git diff-index --quiet HEAD \
|| git commit -a -q -m "$TSTAMP" ) || die COMMIT
echo "Done."
else
echo "Removing empty dir $DIR ..."
rmdir $DIR
fi
done
echo "Running 'git gc' on pack dir"
du -hs $DOT_DIR/pack
(cd $DOT_DIR/pack && git gc) || die GC
du -hs $DOT_DIR/pack
echo "All snapshots packed successfully."
else
echo "No unpacked backups found ..."
fi
}
function on_slow_sync_start {
if [ -n "$SLOW_SYNC_START_CMD" ]; then
rm -rf "$SLOW_SYNC_FILE"
(sleep $SLOW_SYNC_TIME && touch "$SLOW_SYNC_FILE" && eval "$SLOW_SYNC_START_CMD" ; wait) &
disown
shell_pid=$!
fi
}
function on_slow_sync_stop {
if [ -n "$shell_pid" ]; then
kill $shell_pid &>/dev/null
if [[ -n "$SLOW_SYNC_STOP_CMD" && -f "$SLOW_SYNC_FILE" ]]; then
(eval "$SLOW_SYNC_STOP_CMD") &
fi
fi
}
function cron {
DISPLAY=:0.0 sync 2>&1 | timestamp >>"$DOT_DIR/log"
}
function timestamp {
while read -r data
do
echo "[$(date +"%D %T")] $data"
done
}
function acquire_lock {
if ! mkdir "$LOCK_DIR" 2>/dev/null
then
if kill -0 $(cat "$LOCK_DIR/pid") &>/dev/null
then
echo "There's already an instance of BitPocket syncing this directory. Exiting."
exit 1
else
if [[ "${OPTIONS[*]}" =~ force ]]
then
echo -e "${YELLOW}Removing stale, local lock file${CLEAR}"
rm "$LOCK_DIR/pid" && rmdir "$LOCK_DIR" && acquire_lock && return 0
fi
echo -e "${RED}bitpocket error:${CLEAR} Bitpocket found a stale lock directory:"
echo " | Root dir: $(pwd)"
echo " | Lock dir: $LOCK_DIR"
echo " | Command: LOCK_PATH=$(pwd)/$LOCK_DIR && rm \$LOCK_PATH/pid && rmdir \$LOCK_PATH"
echo "Please remove the lock directory and try again."
exit 2
fi
fi
echo $$ > "$LOCK_DIR/pid"
}
function release_lock {
rm "$LOCK_DIR/pid" &>/dev/null && rmdir "$LOCK_DIR" &>/dev/null
}
function acquire_remote_lock {
# TODO: Place the local hostname and this PID in a file, which will make
# automatic lock file cleanup possible. It will also offer better output if
# another host is truly syncing with the remote host.
local INFO="$HOSTNAME:$$:$TIMESTAMP"
local REMOTE_INFO=$($REMOTE_RUNNER "
mkdir -p '$REMOTE_TMP_DIR' && cd '$REMOTE_PATH'
[[ -d '$LOCK_DIR' ]] || mkdir '$LOCK_DIR'
[[ -e '$LOCK_DIR'/remote ]] || echo '$INFO' > '$LOCK_DIR'/remote
cat '$LOCK_DIR'/remote")
[[ "$INFO" == "$REMOTE_INFO" ]] && return 0
IFS=":" read -ra INFO <<< "$REMOTE_INFO"
# From here down, assume the lock could not be acquired
local code=3
if [[ -z $REMOTE_INFO ]]
then
echo "Couldn't acquire remote lock or lock file couldn't be created. Exiting."
elif [[ "$HOSTNAME" != "${INFO[0]}" ]]
then
echo -e "${YELLOW}Another client is syncing with '$REMOTE'${CLEAR}"
echo ">> Host: ${INFO[0]}"
echo ">> PID: ${INFO[1]}"
echo ">> Started: ${INFO[2]}"
elif [[ "$$" != "${INFO[1]}" ]]
then
# This host is syncing with the remote host. Check if the PID is still running
if kill -0 "${INFO[1]}" &>/dev/null
then
# XXX: This should be handled in the `acquire_lock` function
echo "Another instance of Bitpocket is currently syncing this" \
"host with '$REMOTE'"
code=1
else
# In this case, this host is holding the lock with the remote server
# but the sync is no longer running. It is perhaps possible to remove
# the lock?
if [[ "${OPTIONS[*]}" =~ force ]]
then
echo -e "${YELLOW}Removing stale, remote lock file${CLEAR}"
$REMOTE_RUNNER "cd '$REMOTE_PATH' && rm '$LOCK_DIR/remote' && rmdir '$LOCK_DIR'"
# Try again
acquire_remote_lock && return 0
fi
echo "The remote lock is held by this host and is stale." \
"It should be removed, and the sync should be retried."
code=6
fi
fi
release_lock
exit $code
}
function release_remote_lock {
$REMOTE_RUNNER "cd \"$REMOTE_PATH\" && grep -q '$HOSTNAME:$$' '$LOCK_DIR/remote' && rm '$LOCK_DIR/remote' && rmdir '$LOCK_DIR' &>/dev/null"
}
function assert_dotdir {
if [ ! -d "$DOT_DIR" ]; then
echo "fatal: Not a bitpocket directory. Try 'bitpocket help' for usage."
exit 128
fi
mkdir -p "$TMP_DIR"
mkdir -p "$STATE_DIR"
}
function detect_fatfs {
# Find the local mountpoint
if [[ $LOCAL_MOUNTPOINT == false ]]
then
if builtin type -p findmnt &> /dev/null
then
LOCAL_MOUNTPOINT=$(until findmnt . >/dev/null; do cd .. ; done && findmnt -no TARGET .)
else
LOCAL_MOUNTPOINT=$(until $(mount | grep -Ew "$(pwd -P)" >/dev/null); do cd .. ; done && pwd -P)
fi
fi
# Detect local mount is FAT and add appropriate
local fsinfo=($(mount | grep -Ew "${LOCAL_MOUNTPOINT}"))
local fstype=${fsinfo[4]}
if [[ $fstype == *fat ]]
then
RSYNC_OPTS="${RSYNC_OPTS} --no-perms --no-owner --no-group --modify-window=2"
fi
# TODO: Consider remote filesystem type?
}
function assert_mountpoints {
if [[ ${REMOTE_MOUNTPOINT} != false ]]
then
# Sanity check -- ensure mountpoint is a parent of local target
if [[ "${REMOTE_PATH:0:${#REMOTE_MOUNTPOINT}}" != "${REMOTE_MOUNTPOINT}" ]]
then
echo -e "${YELLOW}warning: Remote mount point is not a parent of '${REMOTE_PATH}'${CLEAR}"
fi
$REMOTE_RUNNER "mount | grep -E '\s${REMOTE_MOUNTPOINT}\s'" &> /dev/null
if [[ $? != 0 ]]
then
echo -e "${RED}fatal: Remote sync target is not mounted${CLEAR}"
exit 4
fi
fi
}
function cleanup {
release_lock
release_remote_lock
}
##
# Inspect the state file tree-prev to see if the format of the file is the
# current version (2) or the original version (1). This is used in the
# `strip_mode` function to optimize the sync process when the `tree-prev` file
# uses the current state version.
function check_state_version() {
# In the original state files, the start of the line was the filename with
# a leading slash
if [[ -s "$STATE_DIR/tree-prev" ]]; then
local first=$(head -1 "$STATE_DIR/tree-prev" 2>/dev/null)
if [[ ${first:0:1} == "/" ]]; then
STATE_VERSION=1
fi
fi
}
function bring_the_children_let_me_kill_them {
if [ -n "$shell_pid" ]; then
pkill -P $shell_pid &>/dev/null
kill $shell_pid &>/dev/null
fi
}
function die {
cleanup
bring_the_children_let_me_kill_them
echo -e "${RED}fatal${CLEAR}: command failed $1"
exit 128
}
function intr_cleanup {
die "${YELLOW}Interrupted${CLEAR}"
}
trap intr_cleanup SIGINT
# List all files in the sync set
function list {
echo -e "${GREEN}bitpocket${CLEAR} will sync the following files:"
rsync -av --list-only --exclude "/$DOT_DIR" $USER_RULES . \
| scrub_rsync_list \
| strip_mode \
| sort
}
function usage {
cat <<EOF
usage: bitpocket { init [<REMOTE_HOST>] <REMOTE_PATH>
| sync | help | pack | log | cron | list }
Available commands:
sync Run the sync process. If no command is specified, sync is run by
default.
init Initialize a new bitpocket folder. Requires path and optional
remote host params. Remote path must already exist.
pack Pack any existing (automatic) backups into a git repository.
cron Run sync optimized for cron, logging output to file instead of
stdout.
log Display the log generated by the cron command
list List all files in the sync set (honoring include/exclude/filter
config).
help Show this message.
Options:
-f, --force Clean up stale lock files automatically
-p, --pretend Don't really perform the sync or update the current
state. Instead, show what would be synchronized.
Note: All commands (apart from help), must be run in the root of a
new or existing bitpocket directory structure.
EOF
}
function parseargs() {
while [[ -n $1 ]]; do
case $1 in
# Switches and configuration
-p|--pretend) OPTIONS+=('pretend');;
-f|--force) OPTIONS+=('force');;
-h|--help|-*) COMMANDS+=('help');;
# Arguments (commands)
init) if [[ $# -lt 2 ]]; then
echo "usage: bitpocket init [<REMOTE_HOST>] <REMOTE_PATH>"
exit 128
fi
COMMANDS+=("$1")
ARGS+=("$2")
if [[ $# -gt 2 ]]; then
ARGS+=("$3")
shift;
fi
shift;;
sync|pack|cron|log|list|help)
COMMANDS+=("$1");;
# Anything else
*) echo "!!! Invalid command: $1";;
esac
shift
done
}
parseargs "$@"
# By default, run the sync process
[[ ${#COMMANDS} == 0 ]] && COMMANDS+=('sync')
# For now, only one command really makes sense
case ${COMMANDS[0]} in
init) init "${ARGS[@]}";;
pack) pack;;
log) log;;
cron) cron;;
list) list;;
help) usage;;
sync) sync;;
esac
# Remove temp bitignore files
if [[ -n $tmp_bitignore_files ]]; then
rm $tmp_bitignore_files
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment