#!/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 hat = require('hat') 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') var zeroFill = require('zero-fill') // BitTorrent client version string (used in peer ID). // Generated from package.json major and minor version. For example: // '0.16.1' -> '0016' // '1.2.5' -> '0102' // var VERSION = require('./package.json').version .match(/([0-9]+)/g).slice(0, 2).map(zeroFill(2)).join('') function die (error) { console.error(error) process.exit(1) } var bootstrap = ['dht.gittorrent.org:6881', 'core.gittorrent.org:6881'] var dht = new DHT({ bootstrap: bootstrap }) dht.listen(6881) var announcedRefs = { master: {} } var userProfile = { repositories: {} } 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', {strict: false}) 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) var myPeerId = new Buffer('-WW' + VERSION + '-' + hat(48), 'utf8') wire.handshake(new Buffer(infoHash), new Buffer(myPeerId)) }) 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: bootstrap}, 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) })