aboutsummaryrefslogtreecommitdiff
path: root/gittorrentd
diff options
context:
space:
mode:
authorChris Ball <chris@printf.net>2015-05-27 16:54:10 -0400
committerChris Ball <chris@printf.net>2015-05-27 16:54:10 -0400
commitedf024a982457bc40862a66e4eda910ad4fff5b3 (patch)
treee7a8cc24cf87f0a7b48edf7d72fca8b475c647c9 /gittorrentd
parent59635ae30cb7982fbfad78aabe3f637b9e8adcae (diff)
Rename! gitswarm->gittorrent
Diffstat (limited to 'gittorrentd')
-rwxr-xr-xgittorrentd198
1 files changed, 198 insertions, 0 deletions
diff --git a/gittorrentd b/gittorrentd
new file mode 100755
index 0000000..1e2352a
--- /dev/null
+++ b/gittorrentd
@@ -0,0 +1,198 @@
+#!/usr/bin/env node
+
+var DHT = require('bittorrent-dht')
+var EC = require('elliptic').ec
+var ed25519 = new EC('ed25519')
+var exec = require('child_process').exec
+var glob = require('glob')
+var fs = require('fs')
+var net = require('net')
+var Protocol = require('bittorrent-protocol')
+var spawn = require('child_process').spawn
+var ut_gittorrent = require('ut_gittorrent')
+var ut_metadata = require('ut_metadata')
+var WebTorrent = require('webtorrent')
+
+function die (error) {
+ console.error(error)
+ process.exit(1)
+}
+
+var dht = new DHT({
+ bootstrap: ['three.printf.net:6881']
+})
+dht.listen(6882)
+
+var announcedRefs = {
+ master: {}
+}
+var userProfile = {
+ name: '',
+ email: '',
+ repositories: {}
+}
+
+// These would be better as execSync, but node didn't get that until 0.12.
+exec('git config user.name', function (error, stdout, stderr) {
+ userProfile.name = stdout.trim()
+ if (error !== null) {
+ die("Couldn't get git user name: " + error)
+ }
+})
+exec('git config user.email', function (error, stdout, stderr) {
+ userProfile.email = stdout.trim()
+ if (error !== null) {
+ die("Couldn't get git user email: " + error)
+ }
+})
+var key = create_or_read_keyfile()
+
+function create_or_read_keyfile () {
+ var filename = 'ed25519.key'
+ if (!fs.existsSync(filename)) {
+ var keypair = new EC('ed25519').genKeyPair()
+ fs.writeFileSync(filename, JSON.stringify({
+ pub: keypair.getPublic('hex'),
+ priv: keypair.getPrivate('hex')
+ }))
+ }
+
+ // Okay, now the file exists, whether created here or not.
+ var key = JSON.parse(fs.readFileSync('ed25519.key').toString())
+ return ed25519.keyPair({
+ priv: key.priv,
+ privEnc: 'hex',
+ pub: key.pub,
+ pubEnc: 'hex'
+ })
+}
+
+function bpad (n, buf) {
+ if (buf.length === n) return buf
+ if (buf.length < n) {
+ var b = new Buffer(n)
+ buf.copy(b, n - buf.length)
+ for (var i = 0; i < n - buf.length; i++) b[i] = 0
+ return b
+ }
+}
+
+dht.on('ready', function () {
+ // Spider all */.git dirs and announce all refs.
+ var repos = glob.sync('*/.git/git-daemon-export-ok')
+ var count = repos.length
+ repos.forEach(function (repo) {
+ console.log('in repo ' + repo)
+ repo = repo.replace(/git-daemon-export-ok$/, '')
+ console.log(repo)
+ var upload = spawn('git-upload-pack', ['--strict', repo])
+ upload.stdout.on('data', function (line) {
+ var lines = line.toString().split('\n')
+ lines.forEach(function (line) {
+ var arr = line.toString().split(' ')
+ if (arr.length === 2) {
+ var sha = arr[0].toString()
+ // First four chars are git-upload-pack's length-of-line metadata.
+ sha = sha.substring(4)
+ var ref = arr[1].toString()
+ if (ref.search(/^refs\/heads\//) !== -1 || ref.search(/^refs\/remotes\//) !== -1) {
+ if (!announcedRefs.master[sha]) {
+ console.log('Announcing ' + sha + ' for ref ' + ref + ' on repo ' + repo)
+ announcedRefs.master[sha] = repo
+ var reponame = repo.replace(/\/.git\/$/, '')
+ userProfile.repositories[reponame] = {}
+ userProfile.repositories[reponame].master = sha
+ // Callback counting for repos
+ count--
+ if (count <= 0) {
+ publish_mutable_key()
+ }
+ dht.announce(sha, 30000, function (err) {
+ if (err !== null) {
+ console.log('Announced ' + sha)
+ }
+ })
+ }
+ }
+ }
+ })
+ })
+ upload.stdout.on('end', function () {
+ console.log('end')
+ })
+ upload.on('exit', function (code) {
+ if (code !== 0) {
+ die('Failed: ' + code)
+ }
+ })
+ })
+
+ function publish_mutable_key () {
+ var json = JSON.stringify(userProfile)
+ if (json.length > 950) {
+ console.error("Can't publish mutable key: doesn't fit in 950 bytes.")
+ return false
+ }
+ var value = new Buffer(json.length)
+ value.write(json)
+ var sig = key.sign(value)
+ var opts = {
+ k: bpad(32, Buffer(key.getPublic().x.toArray())),
+ seq: 0,
+ v: value,
+ sig: Buffer.concat([
+ bpad(32, Buffer(sig.r.toArray())),
+ bpad(32, Buffer(sig.s.toArray()))
+ ])}
+ console.log(json)
+ dht.put(opts, function (errors, hash) {
+ console.error('errors=', errors)
+ console.log('hash=', hash.toString('hex'))
+ })
+ }
+
+ net.createServer(function (socket) {
+ var wire = new Protocol()
+ wire.use(ut_gittorrent())
+ wire.use(ut_metadata())
+ socket.pipe(wire).pipe(socket)
+ wire.on('handshake', function (infoHash, peerId) {
+ console.log('Received handshake for ' + infoHash)
+ wire.handshake(new Buffer(infoHash), new Buffer(peerId))
+ })
+ wire.ut_gittorrent.on('generatePack', function (sha) {
+ console.error('calling git pack-objects')
+ var filename = sha + '.pack'
+ var stream = fs.createWriteStream(filename)
+ if (!announcedRefs.master[sha]) {
+ console.error('Asked for an unknown sha!')
+ }
+ var directory = announcedRefs.master[sha]
+ var pack = spawn('git', ['pack-objects', '--revs', '--thin', '--stdout', '--delta-base-offset'], {cwd: directory})
+ pack.on('close', function (code) {
+ if (code !== 0) {
+ console.error('git pack-objects process exited with code ' + code)
+ } else {
+ console.error('Finished writing ' + filename)
+ var webtorrent = new WebTorrent({
+ dht: {bootstrap: ['three.printf.net:6882']},
+ tracker: false
+ })
+ webtorrent.seed(filename, function onTorrent (torrent) {
+ console.error(torrent.infoHash)
+ wire.ut_gittorrent.sendTorrent(torrent.infoHash)
+ })
+ }
+ })
+ pack.stdout.pipe(stream)
+ pack.stderr.on('data', function (data) {
+ console.error(data.toString())
+ })
+ pack.on('exit', function () {
+ console.log('exited')
+ })
+ pack.stdin.write(sha + '\n')
+ pack.stdin.write('--not\n\n')
+ })
+ }).listen(30000)
+})