diff options
| author | Chris Ball <chris@printf.net> | 2015-05-27 16:54:10 -0400 |
|---|---|---|
| committer | Chris Ball <chris@printf.net> | 2015-05-27 16:54:10 -0400 |
| commit | edf024a982457bc40862a66e4eda910ad4fff5b3 (patch) | |
| tree | e7a8cc24cf87f0a7b48edf7d72fca8b475c647c9 /gittorrentd | |
| parent | 59635ae30cb7982fbfad78aabe3f637b9e8adcae (diff) | |
Rename! gitswarm->gittorrent
Diffstat (limited to 'gittorrentd')
| -rwxr-xr-x | gittorrentd | 198 |
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) +}) |
