summarylogtreecommitdiffstats
diff options
context:
space:
mode:
authorskydrome2015-06-01 15:47:44 -0400
committerskydrome2015-06-01 15:47:44 -0400
commitdfe7859f845cabe6ea68ea8d38290450cd52d0bd (patch)
treea776e6187aa8f672bdb405d4e6d717ce89535fca
downloadaur-dfe7859f845cabe6ea68ea8d38290450cd52d0bd.tar.gz
Initial commit
-rw-r--r--.SRCINFO46
-rwxr-xr-x0001-multiple-introducer-support.patch305
-rwxr-xr-x0002-proxy-support.patch36
-rwxr-xr-x0003-allow-non-routable.patch34
-rwxr-xr-xPKGBUILD87
-rwxr-xr-xexclude_buildtest_package.patch38
-rwxr-xr-xtahoe-repair-all.sh50
7 files changed, 596 insertions, 0 deletions
diff --git a/.SRCINFO b/.SRCINFO
new file mode 100644
index 000000000000..01c13332163a
--- /dev/null
+++ b/.SRCINFO
@@ -0,0 +1,46 @@
+pkgbase = tahoe-lafs-i2p
+ pkgdesc = Secure, decentralized, and fault-tolerant filesystem over the I2P network
+ pkgver = 1.10.0.post26
+ pkgrel = 1
+ url = http://tahoe-lafs.org
+ arch = any
+ license = GPL
+ depends = python2-twisted
+ depends = python2-foolscap-i2p
+ depends = python2-mock
+ depends = python2-pyopenssl>=0.14
+ depends = python2-pyasn1
+ depends = python2-pyasn1-modules
+ depends = python2-crypto
+ depends = python2-cryptography
+ depends = python2-zope-interface
+ depends = python2-setuptools<0.8
+ depends = python2-characteristic
+ depends = python2-service-identity
+ depends = python2-numpy
+ depends = pycryptopp
+ depends = pyutil
+ depends = python2-simplejson
+ depends = python2-cffi
+ depends = python2-enum34
+ depends = python2-pycparser
+ depends = python2-six
+ depends = libffi
+ depends = net-tools
+ depends = nevow
+ depends = zbase32
+ depends = zfec
+ optdepends = grid-updates: helps keep you up to date with latest grid news
+ provides = tahoe-lafs
+ conflicts = tahoe-lafs
+ source = https://tahoe-lafs.org/source/tahoe-lafs/snapshots/allmydata-tahoe-1.10.0.post26.tar.bz2
+ source = exclude_buildtest_package.patch
+ source = http://killyourtv.i2p.us/tahoe-lafs/patches/tahoe-lafs-i2p-1.10.0-r0.patch
+ source = tahoe-repair-all.sh
+ sha256sums = 542830908e6deb66767ef98271bcf067f9787c0013b764caae23c7649b4f3171
+ sha256sums = 469aa0ace523b64449e7d2d52958119a84032c50b07828b3dffbb81dca5327bd
+ sha256sums = 425456a08477da50ef1511ea1b24cc6a529c4abaae345a3da89a31c025d74b80
+ sha256sums = 1525fd5c2ac2c93844f23160e70bb47040a9a8ee7dff8b6fff8ca48b374ac216
+
+pkgname = tahoe-lafs-i2p
+
diff --git a/0001-multiple-introducer-support.patch b/0001-multiple-introducer-support.patch
new file mode 100755
index 000000000000..4154cb017035
--- /dev/null
+++ b/0001-multiple-introducer-support.patch
@@ -0,0 +1,305 @@
+From: Kill Your TV <killyourtv@i2pmail.org>
+Date: Wed, 27 Jun 2012 20:59:05 +0000
+Subject: multiple introducer support
+
+---
+ docs/architecture.rst | 8 +++--
+ docs/configuration.rst | 12 +++++--
+ src/allmydata/client.py | 70 +++++++++++++++++++++++++++++++++------
+ src/allmydata/test/test_web.py | 6 ++--
+ src/allmydata/web/root.py | 26 +++++++++++----
+ src/allmydata/web/welcome.xhtml | 27 +++++++++++----
+ 6 files changed, 118 insertions(+), 31 deletions(-)
+
+diff --git a/docs/architecture.rst b/docs/architecture.rst
+index 9e016e0..d1fba50 100644
+--- a/docs/architecture.rst
++++ b/docs/architecture.rst
+@@ -78,9 +78,13 @@ private key, which are easy to move to a new host in case the original one
+ suffers an unrecoverable hardware problem. Second, even if the private key is
+ lost, clients can be reconfigured to use a new introducer.
+
+-For future releases, we have plans to decentralize introduction, allowing any
+-server to tell a new client about all the others.
++By deploying multiple introducers in a Tahoe-LAFS grid, the above SPoF challenge
++can be overcome. In that case if one introducer fails clients are still be
++able to get announcement about new servers from remaining introducers. This is
++our first step towards implementing a fully distributed introduction.
+
++For future releases, we have plans to enhance our distributed introduction,
++allowing any server to tell a new client about all the others.
+
+ File Encoding
+ =============
+diff --git a/docs/configuration.rst b/docs/configuration.rst
+index 31962d4..3f8f5eb 100644
+--- a/docs/configuration.rst
++++ b/docs/configuration.rst
+@@ -33,6 +33,12 @@ create-client``" command will create an initial ``tahoe.cfg`` file for
+ you. After creation, the node will never modify the ``tahoe.cfg`` file: all
+ persistent state is put in other files.
+
++A second file "BASEDIR/introducers" configures introducers. It is necessary to
++write all FURL entries into this file. Each line in this file contains exactly
++one FURL entry. For backward compatibility reasons, any "introducer.furl"
++entry found in tahoe.cfg file will automatically be copied into this file. Keeping
++any FURL entry in tahoe.cfg file is not recommended for new users.
++
+ The item descriptions below use the following types:
+
+ ``boolean``
+@@ -665,16 +671,16 @@ a legal one.
+ timeout.disconnect = 1800
+ ssh.port = 8022
+ ssh.authorized_keys_file = ~/.ssh/authorized_keys
+-
++
+ [client]
+ introducer.furl = pb://ok45ssoklj4y7eok5c3xkmj@tahoe.example:44801/ii3uumo
+ helper.furl = pb://ggti5ssoklj4y7eok5c3xkmj@helper.tahoe.example:7054/kk8lhr
+-
++
+ [storage]
+ enabled = True
+ readonly = True
+ reserved_space = 10000000000
+-
++
+ [helper]
+ enabled = True
+
+diff --git a/src/allmydata/client.py b/src/allmydata/client.py
+index b78fd7b..7200856 100644
+--- a/src/allmydata/client.py
++++ b/src/allmydata/client.py
+@@ -35,6 +35,8 @@ GiB=1024*MiB
+ TiB=1024*GiB
+ PiB=1024*TiB
+
++MULTI_INTRODUCERS_CFG = "introducers"
++
+ class StubClient(Referenceable):
+ implements(RIStubClient)
+
+@@ -137,7 +139,7 @@ class Client(node.Node, pollmixin.PollMixin):
+ self.started_timestamp = time.time()
+ self.logSource="Client"
+ self.DEFAULT_ENCODING_PARAMETERS = self.DEFAULT_ENCODING_PARAMETERS.copy()
+- self.init_introducer_client()
++ self.init_introducer_clients()
+ self.init_stats_provider()
+ self.init_secrets()
+ self.init_storage()
+@@ -169,13 +171,47 @@ class Client(node.Node, pollmixin.PollMixin):
+ if webport:
+ self.init_web(webport) # strports string
+
+- def init_introducer_client(self):
+- self.introducer_furl = self.get_config("client", "introducer.furl")
+- ic = IntroducerClient(self.tub, self.introducer_furl,
++ def init_introducer_clients(self):
++ self.introducer_furls = []
++ self.warn_flag = False
++ # Try to load ""BASEDIR/introducers" cfg file
++ cfg = os.path.join(self.basedir, MULTI_INTRODUCERS_CFG)
++ if os.path.exists(cfg):
++ f = open(cfg, 'r')
++ for introducer_furl in f.read().split('\n'):
++ if not introducer_furl.strip():
++ continue
++ self.introducer_furls.append(introducer_furl)
++ f.close()
++ furl_count = len(self.introducer_furls)
++ #print "@icfg: furls: %d" %furl_count
++
++ # read furl from tahoe.cfg
++ ifurl = self.get_config("client", "introducer.furl", None)
++ if ifurl and ifurl not in self.introducer_furls:
++ self.introducer_furls.append(ifurl)
++ f = open(cfg, 'a')
++ f.write(ifurl)
++ f.write('\n')
++ f.close()
++ if furl_count > 1:
++ self.warn_flag = True
++ self.log("introducers config file modified.")
++ print "Warning! introducers config file modified."
++
++ # create a pool of introducer_clients
++ self.introducer_clients = []
++ for introducer_furl in self.introducer_furls:
++ ic = IntroducerClient(self.tub, introducer_furl,
+ self.nickname,
+ str(allmydata.__full_version__),
+ str(self.OLDEST_SUPPORTED_VERSION))
+- self.introducer_client = ic
++ self.introducer_clients.append(ic)
++ # init introducer_clients as usual
++ for ic in self.introducer_clients:
++ self.init_introducer_client(ic)
++
++ def init_introducer_client(self, ic):
+ # hold off on starting the IntroducerClient until our tub has been
+ # started, so we'll have a useful address on our RemoteReference, so
+ # that the introducer's status page will show us.
+@@ -263,7 +299,9 @@ class Client(node.Node, pollmixin.PollMixin):
+ furl_file = os.path.join(self.basedir, "private", "storage.furl").encode(get_filesystem_encoding())
+ furl = self.tub.registerReference(ss, furlFile=furl_file)
+ ri_name = RIStorageServer.__remote_name__
+- self.introducer_client.publish(furl, "storage", ri_name)
++ # Now, publish multiple introducers
++ for ic in self.introducer_clients:
++ ic.publish(furl, "storage", ri_name)
+ d.addCallback(_publish)
+ d.addErrback(log.err, facility="tahoe.init",
+ level=log.BAD, umid="aLGBKw")
+@@ -316,7 +354,10 @@ class Client(node.Node, pollmixin.PollMixin):
+ # check to see if we're supposed to use the introducer too
+ if self.get_config("client-server-selection", "use_introducer",
+ default=True, boolean=True):
+- sb.use_introducer(self.introducer_client)
++
++ # Now, use our multiple introducers
++ for ic in self.introducer_clients:
++ sb.use_introducer(ic)
+
+ def get_storage_broker(self):
+ return self.storage_broker
+@@ -329,7 +370,9 @@ class Client(node.Node, pollmixin.PollMixin):
+ sc = StubClient()
+ furl = self.tub.registerReference(sc)
+ ri_name = RIStubClient.__remote_name__
+- self.introducer_client.publish(furl, "stub_client", ri_name)
++ # Now publish our multiple introducers
++ for ic in self.introducer_clients:
++ ic.publish(furl, "stub_client", ri_name)
+ d = self.when_tub_ready()
+ d.addCallback(_publish)
+ d.addErrback(log.err, facility="tahoe.init",
+@@ -469,10 +512,15 @@ class Client(node.Node, pollmixin.PollMixin):
+ def get_encoding_parameters(self):
+ return self.DEFAULT_ENCODING_PARAMETERS
+
++ # In case we configure multiple introducers
+ def connected_to_introducer(self):
+- if self.introducer_client:
+- return self.introducer_client.connected_to_introducer()
+- return False
++ status = []
++ if self.introducer_clients:
++ s = False
++ for ic in self.introducer_clients:
++ s = ic.connected_to_introducer()
++ status.append(s)
++ return status
+
+ def get_renewal_secret(self): # this will go away
+ return self._secret_holder.get_renewal_secret()
+diff --git a/src/allmydata/test/test_web.py b/src/allmydata/test/test_web.py
+index 1c7dada..52656b1 100644
+--- a/src/allmydata/test/test_web.py
++++ b/src/allmydata/test/test_web.py
+@@ -42,7 +42,7 @@ from allmydata.introducer import IntroducerNode
+ # create a fake uploader/downloader, and a couple of fake dirnodes, then
+ # create a webserver that works against them
+
+-timeout = 480 # Most of these take longer than 240 seconds on Francois's arm box.
++timeout = 960 # allmydata.test.test_web.Grid.test_deep_check took longer than 480 seconds on zomp
+
+ unknown_rwcap = u"lafs://from_the_future_rw_\u263A".encode('utf-8')
+ unknown_rocap = u"ro.lafs://readonly_from_the_future_ro_\u263A".encode('utf-8')
+@@ -171,13 +171,13 @@ class FakeClient(Client):
+ self.all_contents = {}
+ self.nodeid = "fake_nodeid"
+ self.nickname = "fake_nickname"
+- self.introducer_furl = "None"
++ self.introducer_furls = "None"
+ self.stats_provider = FakeStatsProvider()
+ self._secret_holder = SecretHolder("lease secret", "convergence secret")
+ self.helper = None
+ self.convergence = "some random string"
+ self.storage_broker = StorageFarmBroker(None, permute_peers=True)
+- self.introducer_client = None
++ self.introducer_clients = None
+ self.history = FakeHistory()
+ self.uploader = FakeUploader()
+ self.uploader.all_contents = self.all_contents
+diff --git a/src/allmydata/web/root.py b/src/allmydata/web/root.py
+index 3b9dd1c..956de67 100644
+--- a/src/allmydata/web/root.py
++++ b/src/allmydata/web/root.py
+@@ -219,12 +219,26 @@ class Root(rend.Page):
+
+ return ctx.tag[ul]
+
+- def data_introducer_furl(self, ctx, data):
+- return self.client.introducer_furl
+- def data_connected_to_introducer(self, ctx, data):
+- if self.client.connected_to_introducer():
+- return "yes"
+- return "no"
++ # In case we configure multiple introducers
++ def data_introducers(self, ctx, data):
++ connection_status = []
++ connection_status = self.client.connected_to_introducer()
++ s = []
++ furls = self.client.introducer_furls
++ for furl in furls:
++ if connection_status:
++ i = furls.index(furl)
++ s.append((furl, bool(connection_status[i])))
++ s.sort()
++ return s
++
++ def render_introducers_row(self, ctx, s):
++ (furl, connected) = s
++ status = ("No", "Yes")
++ ctx.fillSlots("introducer_furl", "%s" % (furl))
++ ctx.fillSlots("connected-bool", "%s" % (connected))
++ ctx.fillSlots("connected", "%s" % (status[int(connected)]))
++ return ctx.tag
+
+ def data_helper_furl(self, ctx, data):
+ try:
+diff --git a/src/allmydata/web/welcome.xhtml b/src/allmydata/web/welcome.xhtml
+index 5fd3cdd..a3b546c 100644
+--- a/src/allmydata/web/welcome.xhtml
++++ b/src/allmydata/web/welcome.xhtml
+@@ -35,16 +35,31 @@
+ <div n:render="download_form" />
+ </div>
+
++<h2>Connected Introducer(s)</h2>
++
++<div>
++<table n:render="sequence" n:data="introducers">
++ <tr n:pattern="header">
++ <td>Introducer FURL</td>
++ <td>Connected?</td>
++ </tr>
++ <tr n:pattern="item" n:render="introducers_row">
++ <td><tt><n:slot name="introducer_furl"/></tt></td>
++ <td>
++ <n:slot name="connected"/>
++ <n:attr name="class">service-connected connected-<n:slot name="connected-bool"/></n:attr>
++ </td>
++ </tr>
++ <tr n:pattern="empty"><td>no introducers!</td></tr>
++</table>
++</div>
++
++
++
+ <div class="section" id="grid">
+ <h2>Status of the Storage Grid</h2>
+
+ <div>
+- <n:attr name="class">connected-<n:invisible n:render="string" n:data="connected_to_introducer" /></n:attr>
+- <div>Introducer: <span class="data-chars" n:render="string" n:data="introducer_furl" /></div>
+- <div>Connected to introducer?: <span n:render="string" n:data="connected_to_introducer" /></div>
+- </div>
+-
+- <div>
+ <n:attr name="class">connected-<n:invisible n:render="string" n:data="connected_to_helper" /></n:attr>
+ <div>Helper: <span n:render="string" n:data="helper_furl" /></div>
+ <div>Connected to helper?: <span n:render="string" n:data="connected_to_helper" /></div>
diff --git a/0002-proxy-support.patch b/0002-proxy-support.patch
new file mode 100755
index 000000000000..fe8c111f37c5
--- /dev/null
+++ b/0002-proxy-support.patch
@@ -0,0 +1,36 @@
+From: Kill Your TV <killyourtv@i2pmail.org>
+Date: Wed, 27 Jun 2012 20:59:53 +0000
+Subject: proxy support
+
+---
+ src/allmydata/node.py | 4 ++++
+ src/allmydata/scripts/create_node.py | 1 +
+ 2 files changed, 5 insertions(+)
+
+diff --git a/src/allmydata/node.py b/src/allmydata/node.py
+index a971c4e..84d1a98 100644
+--- a/src/allmydata/node.py
++++ b/src/allmydata/node.py
+@@ -185,6 +185,10 @@ class Node(service.MultiService):
+ # N.B.: this is in seconds, so use "1800" to get 30min
+ self.tub.setOption("disconnectTimeout", int(disconnect_timeout_s))
+
++ http_proxy = self.get_config("node", "http_proxy", "")
++ if http_proxy:
++ self.tub.setOption("http-proxy", http_proxy)
++
+ self.nodeid = b32decode(self.tub.tubID.upper()) # binary format
+ self.write_config("my_nodeid", b32encode(self.nodeid).lower() + "\n")
+ self.short_nodeid = b32encode(self.nodeid).lower()[:8] # ready for printing
+diff --git a/src/allmydata/scripts/create_node.py b/src/allmydata/scripts/create_node.py
+index 7e9dcf7..09ef9bf 100644
+--- a/src/allmydata/scripts/create_node.py
++++ b/src/allmydata/scripts/create_node.py
+@@ -91,6 +91,7 @@ def write_node_config(c, config):
+ webport = ""
+ c.write("web.port = %s\n" % (webport.encode('utf-8'),))
+ c.write("web.static = public_html\n")
++ c.write("#http_proxy =\n")
+ c.write("#tub.port =\n")
+ c.write("#tub.location = \n")
+ c.write("#log_gatherer.furl =\n")
diff --git a/0003-allow-non-routable.patch b/0003-allow-non-routable.patch
new file mode 100755
index 000000000000..b8b30138f19b
--- /dev/null
+++ b/0003-allow-non-routable.patch
@@ -0,0 +1,34 @@
+From: Kill Your TV <killyourtv@i2pmail.org>
+Date: Wed, 27 Jun 2012 20:00:22 +0000
+Subject: allow non-routable
+
+---
+ src/allmydata/node.py | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+diff --git a/src/allmydata/node.py b/src/allmydata/node.py
+index 84d1a98..6ea303a 100644
+--- a/src/allmydata/node.py
++++ b/src/allmydata/node.py
+@@ -280,7 +280,11 @@ class Node(service.MultiService):
+
+ service.MultiService.startService(self)
+ d = defer.succeed(None)
+- d.addCallback(lambda res: iputil.get_local_addresses_async())
++ location = self.get_config("node", "tub.location", None)
++ if location == "":
++ d.addCallback(lambda res: ['127.0.0.1'])
++ else:
++ d.addCallback(lambda res: iputil.get_local_addresses_async())
+ d.addCallback(self._setup_tub)
+ def _ready(res):
+ self.log("%s running" % self.NODETYPE)
+@@ -356,6 +360,8 @@ class Node(service.MultiService):
+ base_location = ",".join([ "%s:%d" % (addr, portnum)
+ for addr in local_addresses ])
+ location = self.get_config("node", "tub.location", base_location)
++ if location == "":
++ location = base_location
+ self.log("Tub location set to %s" % location)
+ self.tub.setLocation(location)
+
diff --git a/PKGBUILD b/PKGBUILD
new file mode 100755
index 000000000000..6d33e5985cf7
--- /dev/null
+++ b/PKGBUILD
@@ -0,0 +1,87 @@
+# Maintainer : Skydrome <skydrome@i2pmail.org>
+# Contributor: DaNiMoTh <jjdanimoth@gmail.com>
+
+pkgname=tahoe-lafs-i2p
+pkgver=1.10.0.post26
+_patchver=1.10.0-r0
+pkgrel=1
+pkgdesc="Secure, decentralized, and fault-tolerant filesystem over the I2P network"
+url='http://tahoe-lafs.org'
+license=('GPL')
+arch=('any')
+conflicts=('tahoe-lafs')
+provides=('tahoe-lafs')
+BUILDENV+=(!check)
+
+depends=('python2-twisted'
+ 'python2-foolscap-i2p'
+ 'python2-mock'
+ 'python2-pyopenssl>=0.14'
+ 'python2-pyasn1'
+ 'python2-pyasn1-modules'
+ 'python2-crypto'
+ 'python2-cryptography'
+ 'python2-zope-interface'
+ 'python2-setuptools<0.8'
+ 'python2-characteristic'
+ 'python2-service-identity'
+ 'python2-numpy'
+ 'pycryptopp'
+ 'pyutil'
+ 'python2-simplejson'
+ 'python2-cffi'
+ 'python2-enum34'
+ 'python2-pycparser'
+ 'python2-six'
+ 'libffi'
+ 'net-tools' # provides /sbin/ifconfig
+ 'nevow'
+ 'zbase32'
+ 'zfec')
+
+optdepends=('grid-updates: helps keep you up to date with latest grid news')
+
+source=("https://tahoe-lafs.org/source/tahoe-lafs/snapshots/allmydata-tahoe-${pkgver}.tar.bz2"
+ #"setuptools_fix.diff"
+ #'0001-multiple-introducer-support.patch'
+ #'0002-proxy-support.patch'
+ #'0003-allow-non-routable.patch'
+ 'exclude_buildtest_package.patch'
+ "http://killyourtv.i2p.us/tahoe-lafs/patches/tahoe-lafs-i2p-${_patchver}.patch"
+ 'tahoe-repair-all.sh')
+
+sha256sums=('542830908e6deb66767ef98271bcf067f9787c0013b764caae23c7649b4f3171'
+ #'6d37e7003bb91c07963a71fde217b5e4f5b6eb638f6d602d82dae7a13bcaf959'
+ '469aa0ace523b64449e7d2d52958119a84032c50b07828b3dffbb81dca5327bd'
+ '425456a08477da50ef1511ea1b24cc6a529c4abaae345a3da89a31c025d74b80'
+ '1525fd5c2ac2c93844f23160e70bb47040a9a8ee7dff8b6fff8ca48b374ac216')
+
+prepare() {
+ cd "${srcdir}/allmydata-tahoe-${pkgver}"
+ #patch -Np0 -i ../setuptools_fix.diff
+ if [[ ! -r "${srcdir}/tahoe-lafs-i2p-${_patchver}.patch" ]]; then
+ for p in $(ls ${srcdir}/*.patch); do
+ patch -Np1 -i $p
+ done
+ else
+ patch -Np1 -i "${srcdir}/tahoe-lafs-i2p-${_patchver}.patch"
+ fi
+}
+
+build(){
+ cd "${srcdir}/allmydata-tahoe-${pkgver}"
+ python2 setup.py build
+}
+
+check() {
+ cd "${srcdir}/allmydata-tahoe-${pkgver}"
+ msg "This may take a while"
+ python2 bin/tahoe debug trial $MAKEFLAGS
+}
+
+package(){
+ cd "${srcdir}/allmydata-tahoe-${pkgver}"
+ python2 setup.py install --root="$pkgdir" --optimize=1
+ install -Dm644 COPYING.GPL "${pkgdir}/usr/share/licenses/${pkgname}/COPYING"
+ install -Dm755 "$srcdir/tahoe-repair-all.sh" "${pkgdir}/usr/bin/tahoe-repair-all"
+}
diff --git a/exclude_buildtest_package.patch b/exclude_buildtest_package.patch
new file mode 100755
index 000000000000..c28b331ba08d
--- /dev/null
+++ b/exclude_buildtest_package.patch
@@ -0,0 +1,38 @@
+ By default tahoe-lafs also install a buildtest package but this isn't relevant
+ in the context of the Debian package.
+Author: bertagaz <bertagaz@ptitcanardnoir.org>
+Index: tahoe/setup.py
+===================================================================
+--- tahoe.orig/setup.py 2012-07-04 21:42:07.000000000 +0200
++++ tahoe/setup.py 2012-07-04 21:59:45.051169297 +0200
+@@ -467,8 +467,7 @@
+ 'allmydata.util',
+ 'allmydata.web',
+ 'allmydata.web.static',
+- 'allmydata.windows',
+- 'buildtest'],
++ 'allmydata.windows'],
+ classifiers=trove_classifiers,
+ test_suite="allmydata.test",
+ install_requires=install_requires,
+Index: tahoe/src/allmydata_tahoe.egg-info/SOURCES.txt
+===================================================================
+--- tahoe.orig/src/allmydata_tahoe.egg-info/SOURCES.txt 2012-07-04 21:41:43.000000000 +0200
++++ tahoe/src/allmydata_tahoe.egg-info/SOURCES.txt 2012-07-04 21:59:25.615659017 +0200
+@@ -431,7 +431,5 @@
+ src/allmydata_tahoe.egg-info/not-zip-safe
+ src/allmydata_tahoe.egg-info/requires.txt
+ src/allmydata_tahoe.egg-info/top_level.txt
+-src/buildtest/__init__.py
+-src/buildtest/test_build_with_fake_dist.py
+ static/tahoe.py
+-twisted/plugins/allmydata_trial.py
+\ No newline at end of file
++twisted/plugins/allmydata_trial.py
+Index: tahoe/src/allmydata_tahoe.egg-info/top_level.txt
+===================================================================
+--- tahoe.orig/src/allmydata_tahoe.egg-info/top_level.txt 2012-07-04 21:41:43.295659068 +0200
++++ tahoe/src/allmydata_tahoe.egg-info/top_level.txt 2012-07-04 21:59:25.615659017 +0200
+@@ -1,2 +1 @@
+ allmydata
+-buildtest
diff --git a/tahoe-repair-all.sh b/tahoe-repair-all.sh
new file mode 100755
index 000000000000..ab8e7673f849
--- /dev/null
+++ b/tahoe-repair-all.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+##########################################################
+# Stop multiple instances from running simultaneously #
+# Nothing in this section should require editing #
+##########################################################
+if [ -w /var/lock ]; then # the default lock directory in Linux
+ LOCKDIR="/var/lock/tahoe-repair-all.lck"
+else
+ LOCKDIR="/tmp/tahoe-repair-all.lck" # but maybe not elsewhere...
+fi
+PIDFILE="${LOCKDIR}/PID"
+ENO_SUCCESS=0;
+ENO_GENERAL=1;
+ENO_LOCKFAIL=2;
+ENO_RECVSIG=3;
+
+if mkdir "${LOCKDIR}" > /dev/null 2>&1 ; then
+ trap 'ECODE=$?;
+ rm -rf "${LOCKDIR}"' 0
+ touch $PIDFILE
+ echo $$ > "${PIDFILE}"
+ trap 'echo "ERROR: Killed by a signal $ECODE $ENO_RECVSIG" >&2
+ exit ${ENO_RECVSIG}' 1 2 3 15
+else
+ # lock failed, check if it's stale
+ OTHERPID="$(cat "${PIDFILE}")"
+
+ if [ $? != 0 ]; then
+ echo "ERROR: Another instance of `basename $0` is active with PID ${OTHERPID}" >&2
+ exit ${ENO_LOCKFAIL}
+ fi
+
+ if ! kill -0 ${OTHERPID} >/dev/null 2>&1; then
+ #stale lock, removing it and restarting
+ echo "INFO: Removing stale PID ${OTHERPID}" >&2
+ rm -rf ${LOCKDIR}
+ echo "INFO: [`basename $0`] restarting" >&2
+ exec "$0" "$@"
+ else
+ #lock is valid and OTHERPID is active
+ echo "ERROR: Another instance of `basename $0` is active with PID ${OTHERPID}" >&2
+ exit ${ENO_LOCKFAIL}
+ fi
+fi
+##########################################################
+for item in `tahoe list-aliases | cut -f 1 -d :`; do
+ echo '*** '"$item"
+ tahoe deep-check -v --repair --add-lease $item: | perl -pe 's/^/\t/'
+ echo
+done