Skip to content

Commit

Permalink
Seems like v0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
ku1ik committed Jul 10, 2011
1 parent 109b2a3 commit c817230
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 80 deletions.
Empty file removed README
Empty file.
63 changes: 63 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# bitpocket

## About

**bitpocket** is a small but smart script that does 2-way directory
synchronization. It uses _rsync_ to do efficient data transfer and tracks local
file creation/removal to avoid known rsync problem when doing 2-way syncing
with deletion.

## Installation

Download script and place it in a directory in your $PATH:

$ curl -sL https://raw.github.com/sickill/bitpocket/master/bin/bitpocket > ~/bin/bitpocket
$ chmod +x ~/bin/bitpocket


## Setting up master

Create empty directory on some host that will be the master copy of your files:

$ ssh example.org
$ mkdir ~/BitPocketMaster


## Setting up slaves

On each machine you want to synchronize initialize empty directory as your bitpocket:

$ mkdir ~/BitPocket
$ cd ~/BitPocket
$ bitpocket init example.org:~/BitPocketMaster


### Manual sync

Now whenever you want to sync with master just run _bitpocket_ inside your
bitpocket directory:

$ cd ~/BitPocket
$ bitpocket


### Automatic sync with cron

Add following line to your crontab to run bitpocket as often as desired:

*/5 * * * * (cd ~/BitPocket; nice ~/bin/bitpocket >>.bitpocket/log)

Note that cron usually has very limited environment and your ssh keys with
passhrases won't work in cron jobs as ssh-agents/keyrings don't work there.
Thus it's preferable to generate passphrase-less ssh key for bitpocket
authentication:

$ cd ~/BitPocket
$ ssh-keygen -t rsa -C bitpocket-`hostname` -N '' -f .bitpocket/id_rsa

and uncomment line with `RSYNC_SSH` in _.bitpocket/config_ file.


## Author

Marcin Kulik / <https://github.com/sickill> / <http://ku1ik.com/>
80 changes: 80 additions & 0 deletions bin/bitpocket
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/bin/bash

export LC_ALL=C # for stable "sort" output

DOTDIR=.bitpocket
CFG_PATH=$DOTDIR/config
LOCK_PATH=$DOTDIR/lock

function init {
if [[ -d $DOTDIR || -f $CFG_PATH ]]; then
echo "fatal: Current directory already initialized for bitpocket"
exit 128
fi

if [ -z $1 ]; then
echo "usage: bitpocket init <REMOTE>"
echo
echo "<REMOTE> can be local path or host:path"
exit 128
fi

mkdir $DOTDIR

echo "REMOTE=\"$1\"" > $CFG_PATH
echo "# export RSYNC_RSH=\"ssh -i .bitpocket/id_rsa\"" >> $CFG_PATH

echo "Initialized bitpocket directory at `pwd`"
}

function save_tree {
find | sort | cut -d '/' -f 2- | grep -v "^\\$DOTDIR" | grep -v '^\.$' > $DOTDIR/$1
}

function sync {
if [ ! -d $DOTDIR ]; then
echo "fatal: Not a bitpocket directory"
exit 128
fi

[ -f $LOCK_PATH ] && kill -0 $(cat $LOCK_PATH) &>/dev/null && exit 0
echo $$ > $LOCK_PATH
. $CFG_PATH

echo -e "\e[1;32mbitpocket\e[0m started at `date`."
echo

# check what has changed
touch $DOTDIR/tree-prev
save_tree "tree-current"

# prevent bringing back locally deleted files
comm -23 $DOTDIR/tree-prev $DOTDIR/tree-current >$DOTDIR/fetch-exclude

# prevent removing new local files
comm -13 $DOTDIR/tree-prev $DOTDIR/tree-current >>$DOTDIR/fetch-exclude

# fetch everything new and updated, locally remove files deleted on remote
rsync -auvzxi --delete --exclude $DOTDIR --exclude-from $DOTDIR/fetch-exclude $REMOTE/ . || die

# send new and updated, remotely remove files deleted locally
rsync -auvzxi --delete --exclude $DOTDIR . $REMOTE/ || die

# prepare for next run
rm $DOTDIR/tree-current
save_tree "tree-prev"

rm $LOCK_PATH
}

function die {
rm $LOCK_PATH
echo "fatal: command failed"
exit 128
}

if [ "$1" = "init" ]; then
init $2
else
sync
fi
134 changes: 54 additions & 80 deletions spec/bitpocket_spec.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
require 'fileutils'

BP_BIN_PATH = File.join(File.dirname(__FILE__), '..', 'bin', 'bitpocket')

def sync
system "[ -d .sinck ] || mkdir .sinck"
system "[ -f .sinck/tree-prev ] || touch .sinck/tree-prev"
system "find | sort | grep -v ./.sinck | grep -v '^\.$' > .sinck/tree-current"
system "comm -23 .sinck/tree-prev .sinck/tree-current | cut -d '/' -f 2- >.sinck/fetch-exclude"
system "comm -13 .sinck/tree-prev .sinck/tree-current | cut -d '/' -f 2- >>.sinck/fetch-exclude"
system "rsync -auvzx --delete --exclude .sinck --exclude-from .sinck/fetch-exclude ../remote/ . &>/dev/null"
system "rsync -auvzx --delete --exclude .sinck . ../remote/ &>/dev/null"
system "rm .sinck/tree-current"
system "find | sort | grep -v ./.sinck | grep -v '^\.$' > .sinck/tree-prev"
system "sh #{BP_BIN_PATH} >/dev/null"
end

def local_path(fname)
Expand Down Expand Up @@ -46,11 +40,17 @@ def rm(path)
FileUtils.mkdir_p(@local_dir)
FileUtils.mkdir_p(@remote_dir)
Dir.chdir(@local_dir)
FileUtils.mkdir_p("#{@local_dir}/.bitpocket")
cat 'REMOTE=../remote', local_path('.bitpocket/config')
end

let(:content) { 'foo' }

it 'does not remove new local files' do
touch local_path('a')

sync

File.exist?(local_path('a')).should be(true)
end

Expand All @@ -59,102 +59,76 @@ def rm(path)
touch remote_path('a')
sync
rm local_path('a')

sync

File.exist?(local_path('a')).should be(false)
end

describe 'creating new local file' do
before do
touch local_path('a')
end
it 'transfers new file from local to remote' do
touch local_path('a')

it 'transfers new file from local to remote' do
sync
sync

File.exist?(local_path('a')).should be(true)
File.exist?(remote_path('a')).should be(true)
end
File.exist?(local_path('a')).should be(true)
File.exist?(remote_path('a')).should be(true)
end

describe 'updating local file' do
let(:content) { 'foo' }

before do
touch local_path('a')
touch remote_path('a')
sync
system "touch -d '00:00' #{remote_path('a')}"
cat content, local_path('a')
end
it 'transfers updated file from local to remote' do
touch local_path('a')
touch remote_path('a')
sync
system "touch -d '00:00' #{remote_path('a')}"
cat content, local_path('a')

it 'transfers updated file from local to remote' do
sync
sync

File.read(local_path('a')).should == content
File.read(remote_path('a')).should == content
end
File.read(local_path('a')).should == content
File.read(remote_path('a')).should == content
end

describe 'creating new remote file' do
before do
touch remote_path('a')
end
it 'transfers new file from remote to local' do
touch remote_path('a')

it 'transfers new file from remote to local' do
sync
sync

File.exist?(local_path('a')).should be(true)
File.exist?(remote_path('a')).should be(true)
end
File.exist?(local_path('a')).should be(true)
File.exist?(remote_path('a')).should be(true)
end

describe 'updating remote file' do
let(:content) { 'foo' }

before do
touch local_path('a')
touch remote_path('a')
sync
cat content, remote_path('a')
end
it 'transfers updated file from remote to local' do
touch local_path('a')
touch remote_path('a')
sync
cat content, remote_path('a')

it 'transfers updated file from remote to local' do
sync
sync

File.read(local_path('a')).should == content
File.read(remote_path('a')).should == content
end
File.read(local_path('a')).should == content
File.read(remote_path('a')).should == content
end

describe 'deleting local file' do
before do
touch local_path('a')
touch remote_path('a')
sync
rm local_path('a')
end
it 'removes file from remote if locally deleted' do
touch local_path('a')
touch remote_path('a')
sync
rm local_path('a')

it 'removes file from remote' do
sync
sync

File.exist?(local_path('a')).should be(false)
File.exist?(remote_path('a')).should be(false)
end
File.exist?(local_path('a')).should be(false)
File.exist?(remote_path('a')).should be(false)
end

describe 'deleting remote file' do
before do
touch local_path('a')
touch remote_path('a')
sync
rm remote_path('a')
end
it 'removes file from local if remotelly deleted' do
touch local_path('a')
touch remote_path('a')
sync
rm remote_path('a')

it 'removes file from local' do
sync
sync

File.exist?(local_path('a')).should be(false)
File.exist?(remote_path('a')).should be(false)
end
File.exist?(local_path('a')).should be(false)
File.exist?(remote_path('a')).should be(false)
end
end

0 comments on commit c817230

Please sign in to comment.