Full Code of mafintosh/torrent-docker for AI

master 79bbefb6220a cached
17 files
26.7 KB
7.6k tokens
1 requests
Download .txt
Repository: mafintosh/torrent-docker
Branch: master
Commit: 79bbefb6220a
Files: 17
Total size: 26.7 KB

Directory structure:
gitextract_ct1_8s_w/

├── .gitignore
├── LICENSE
├── README.md
├── bin/
│   ├── boot.js
│   ├── create.js
│   ├── destroy.js
│   ├── seed.js
│   └── tracker.js
├── bin.js
├── docs/
│   ├── boot.txt
│   ├── create.txt
│   ├── destroy.txt
│   ├── seed.txt
│   └── tracker.txt
├── filesystem.js
├── help.txt
└── package.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
image.tar
image.db
image.db.tgz
index.tgz
node_modules
mnt
container
*.torrent
containers/*


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2014 Mathias Buus

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: README.md
================================================
# torrent-docker

MAD SCIENCE realtime boot of remote docker images using bittorrent

```
npm install -g torrent-docker
torrent-docker --help
```

## HOLD ON TO YOUR BRAIN

Docker images are HUGE. A simple `hello world` node app easily takes up `> 600MB` space.
Downloading/uploading these images can a looong time.

To fix this `torrent-docker` implements a union file system that allows you to mount a docker image
shared using bittorrent and boot a container - all in realtime!

![whoa](http://i.imgur.com/rfFWukr.gif)

## Usage

### Seed a docker image

First create a docker image

```
FROM ubuntu:14.04
RUN apt-get update && apt-get install -qy curl vim
```

Then build it

```
docker build -t test-image .
```

Now all we need to do is create a torrent from the docker image

```
torrent-docker create test-image
```

This creates a file `test-image.torrent` and a data folder `test-image/`.
Share this torrent using your favorite torrent client or do

```
torrent-docker seed test-image.torrent # will print a activity log
```

### Realtime boot the docker image

Now copy `test-image.torrent` to another machine.
To boot the image do

```
torrent-docker boot test-image.torrent my-container
```

This will mount the torrent as a union file system (that is writable!) and boot the docker image.
In addition it will also seed the torrent which means the more containers you boot the more the torrent will be seeded.

You can attach to the debug log to see download speed, how many peers your are connected to, which files are being accessed etc using

```
nc localhost 10000 # will tail the debug log from the boot process
```

After a couple of seconds (depending on your internet connection, ymmw) you should be attached to a bash process
running in your image! If for some reason your boot process cannot find a seeder you can specify them doing

```
torrent-docker boot test-image.torrent my-container --peer 128.199.33.21:6441
```

Optionally you can start your own tracker

```
torrent-docker tracker --port 8080
torrent-docker boot test-image.torrent my-container --tracker 127.0.0.1:8080
```

## Dependencies

On OSX you'll need the following

* boot2docker, https://github.com/boot2docker/boot2docker
* osx fuse, http://sourceforge.net/projects/osxfuse/files/latest/download?source=files
* pkg-config, `brew install pkg-config`

To make `/var`, `/etc` belong to root you need to run the following after installing boot2docker

```
boot2docker ssh
sudo umount /Users
sudo mount -t vboxsf Users /Users/
```

You need to run this everytime you boot boot2docker

## Troubleshooting

THIS IS HIGHLY EXPERIMENTAL.

Currently I have only tested this on OSX using OSX fuse and boot2docker.


================================================
FILE: bin/boot.js
================================================
#!/usr/bin/env node

var torrents = require('torrent-stream')
var filesystem = require('../filesystem')
var mkdirp = require('mkdirp')
var fs = require('fs')
var pretty = require('pretty-bytes')
var proc = require('child_process')
var net = require('net')
var minimist = require('minimist')

var argv = minimist(process.argv.slice(2), {alias:{peer:'p', tracker:'t', nomount:'n'}})
var trackers = argv.t && [].concat(argv.t)

var torrent = argv._[0]
var container = argv._[1]

if (!torrent || !container || argv.help) {
  console.error(fs.readFileSync(__dirname+'/../docs/boot.txt', 'utf-8'))
  process.exit(1)
}

var noMount = [].concat(argv.nomount || [])
var engine = torrents(fs.readFileSync(torrent), {trackers:trackers})
var mnt = container+'/mnt'
var data = container+'/data'

// TODO: remove me - this is the address of registry.mathiasbuus.eu - incase i forget for me demo
// engine.swarm.add('128.199.33.21:6881')

var peers = [].concat(argv.peer || [])
peers.forEach(function(p) {
  engine.swarm.add(p)
})

// engine.on('peer', function(peer) {
//   console.log(peer)
// })

engine.files.forEach(function(f) {
  f.select()
})

engine.listen(function() {
  console.log('engine is listening on port %d', engine.port)
})

mkdirp.sync(mnt)
container = fs.realpathSync(container)

var sockets = []
var server = net.createServer(function(socket) {
  sockets.push(socket)
  socket.on('error', socket.destroy)
  socket.on('close', function() {
    var i = sockets.indexOf(socket)
    if (i > -1) sockets.splice(i, 1)
  })
})

var log = function() {
  var msg = require('util').format.apply(null, arguments)
  sockets.forEach(function(s) {
    s.write(msg+'\n')
  })
}

setInterval(function() {
  log('down: %s/s, up: %s/s, peers: %d', pretty(engine.swarm.downloadSpeed()), pretty(engine.swarm.uploadSpeed()), engine.swarm.wires.length)
}, 1000)

server.listen(10000)
server.once('error', function() {
  freeport(function(err, port) {
    if (err) throw err
    server.listen(port)
  })
})

server.on('listening', function() {
  console.log('mounting container drive here: '+container+'/mnt')
  if (noMount.length) console.log('not mounting: '+noMount.join(' '))
  console.log('access log server by doing: nc localhost %d', server.address().port)
  console.log('downloading filesystem index...')
  filesystem(mnt, data, {
    createImageStream: function(opts) {
      return engine.files[0].createReadStream(opts)
    },
    createIndexStream: function() {
      return engine.files[1].createReadStream()
    },
    log: log,
    uid: argv.uid !== undefined ? Number(argv.uid) : process.getuid(),
    gid: argv.gid !== undefined ? Number(argv.gid) : process.getgid()
  }, function(err, fs) {
    if (err) throw err
    if (argv.docker === false) return console.log('torrent mounted...')
    console.log('filesystem index loaded. booting vm...')  
    fs.readdir('/', function(err, files) {
      if (err) throw err

      files = files
        .filter(function(file) {
          return file !== '.' && file !== '..' && file !== 'proc' && file !== 'dev' && noMount.indexOf(file) === -1
        })
        .map(function(file) {
          return '-v '+container+'/mnt/'+file+':/'+file+' '
        })
        .join('').trim().split(/\s+/)

      var vars = [].concat(argv.e || []).concat(argv.env || [])
      var env = []

      vars.forEach(function(v) {
        env.push('-e', v)
      })

      var spawn = function() {
        proc.spawn('docker', ['run', '--net', argv.net || 'bridge', '-it', '--rm', '--entrypoint=/bin/bash'].concat(env).concat(files).concat('tianon/true'), {stdio:'inherit'}).on('exit', function() {
          process.exit()
        })        
      }

      var ns = new Buffer('nameserver 8.8.8.8\nnameserver 8.8.4.4\n')
      fs.open('/etc/resolv.conf', 1, function(err, fd) {
        if (err < 0) return spawn()
        fs.write('/etc/resolv.conf', 0, ns.length, ns, fd, function(err) {
          if (err < 0) return spawn()
          fs.release('/etc/resolv.conf', fd, spawn)
        })
      })
    })
  })
})


================================================
FILE: bin/create.js
================================================
#!/usr/bin/env node

var docker = require('docker-remote-api')
var zlib = require('zlib')
var level = require('level')
var mkdirp = require('mkdirp')
var rimraf = require('rimraf')
var fs = require('fs')
var lexint = require('lexicographic-integer')
var tar = require('tar-stream')
var tarfs = require('tar-fs')
var createTorrent = require('create-torrent')
var minimist = require('minimist')

var argv = minimist(process.argv.slice(2), {alias:{'announce-list':'a'}})

var image = argv._[0]
var announceList = argv.a && [].concat(argv.a)

if (!image || argv.help) {
  console.error(fs.readFileSync(__dirname+'/../docs/create.txt', 'utf-8'))
  process.exit(1)
}

var request = docker()

var getImage = function(cb) {
  request.post('/containers/create', {
    json: {
      Image: image
    }
  }, function(err, c) {
    if (err) return cb(err)

    request.get('/containers/'+c.Id+'/export', function(err, stream) {
      if (err) return cb(err)

      stream.on('end', function() {
        request.del('/containers/'+c.Id)
      })

      cb(null, stream, c.Id)
    })
  })
}

var dir = image.replace(/[\/:]/g, '-')

var toIndexKey = function(name) {
  var depth = name.split('/').length-1
  return lexint.pack(depth, 'hex')+name
}

console.log('creating a torrent for %s', image)

getImage(function(err, stream, id) {
  if (err) throw err

  console.log('exporting docker image layer')
  mkdirp(dir, function(err) {
    if (err) throw err
    stream.pipe(fs.createWriteStream(dir+'/image.tar')).on('finish', function() {
      console.log('indexing image layer')

      var db = level(dir+'/index')

      fs.createReadStream(dir+'/image.tar').pipe(tar.extract())
        .on('entry', function(header, stream, next) {
          header.name = ('/' + header.name).replace('//', '/').replace(/(.)\/$/, '$1')
          stream.resume()

          var entry = {
            key: toIndexKey(header.name),
            value: {
              name: header.name,
              mode: header.mode,
              type: header.type,
              start: stream.offset,
              size: header.size,
              linkname: header.linkname     
            }
          }

          db.put(entry.key, entry.value, {valueEncoding:'json'}, next)
        })
        .on('finish', function() {
          tarfs.pack(dir+'/index').pipe(zlib.createGzip()).pipe(fs.createWriteStream(dir+'/index.tgz')).on('finish', function() {
            rimraf(dir+'/index', function() {
              opts = {}
              if (!!announceList) {
                opts.announceList = [announceList]
              }
              console.log('generating torrent file')
              createTorrent(dir, opts, function(err, buf) {
                if (err) throw err
                fs.writeFile(dir+'.torrent', buf, function(err) {
                  if (err) throw err
                  console.log('torrent created and written to '+dir+'.torrent')
                })
              })
            })
          })
        })
    })
  })
})

================================================
FILE: bin/destroy.js
================================================
#!/usr/bin/env node

var fuse = require('fuse-bindings')
var rimraf = require('rimraf')
var fs = require('fs')
var minimist = require('minimist')

var argv = minimist(process.argv.slice(2))
var name = argv._[0]

if (!name || argv.help) {
  console.error(fs.readFileSync(__dirname+'/../docs/destroy.txt', 'utf-8'))
  process.exit(0)
}

fuse.unmount(name+'/mnt', function() {
  rimraf.sync(name)
})

================================================
FILE: bin/seed.js
================================================
#!/usr/bin/env node

var torrents = require('torrent-stream')
var pretty = require('pretty-bytes')
var path = require('path')
var mkdirp = require('mkdirp')
var fs = require('fs')
var minimist = require('minimist')

var argv = minimist(process.argv.slice(2), {alias:{peer:'p', tracker:'t', nomount:'n'}})
var trackers = argv.t && [].concat(argv.t)

var torrent = argv._[0]
if (!torrent || argv.help) {
  console.error(fs.readFileSync(__dirname+'/../docs/seed.txt', 'utf-8'))
  process.exit(1)
}

var engine = torrents(fs.readFileSync(torrent), {
  path: path.dirname(torrent),
  trackers: trackers
})

var peers = [].concat(argv.peer || [])
peers.forEach(function(p) {
  engine.swarm.add(p)
})

var peers = 0

engine.on('peer', function(peer) {
  peers++
})

engine.files.forEach(function(f) {
  f.select()
})

engine.listen(function() {
  console.log('seeding on port %d', engine.port)
  setInterval(function() {
    console.log('connected to %d peers. found %d in total. upload: %s', engine.swarm.wires.length, peers, pretty(engine.swarm.uploadSpeed()))
  }, 1000)
})

================================================
FILE: bin/tracker.js
================================================
#!/usr/bin/env node

var minimist = require('minimist')
var tracker = require('bittorrent-tracker/server')
var fs = require('fs')

var argv = minimist(process.argv.slice(2))

if (argv.help) {
  console.error(fs.readFileSync(__dirname+'/../docs/tracker.txt', 'utf-8'))
  process.exit(1)
}

var server = tracker()

server.on('warning', function (err) {
  // client sent bad data. probably not a problem, just a buggy client.
  console.log(err.message)
})

server.on('listening', function (port) {
  console.log('tracker server is now listening on ' + port)
})

// listen for individual tracker messages from peers:

server.on('start', function (addr) {
  console.log('got start message from ' + addr)
})

server.on('complete', function (addr) {
  console.log('got complete message from '+addr)
})

server.on('update', function (addr) {
  console.log('got update message from '+addr)
})

server.on('stop', function (addr) {
  console.log('got stop message from '+addr)
})

// start tracker server listening!
server.listen(argv.port || 80)


================================================
FILE: bin.js
================================================
#!/usr/bin/env node

var cmd = process.argv[2]
var fs = require('fs')

process.argv.splice(2, 1)

if (cmd === 'seed') require('./bin/seed')
else if (cmd === 'create') require('./bin/create')
else if (cmd === 'run' || cmd === 'boot') require('./bin/boot')
else if (cmd === 'destroy') require('./bin/destroy')
else if (cmd === 'tracker') require('./bin/tracker')
else console.log(fs.readFileSync(__dirname+'/help.txt', 'utf-8'))

================================================
FILE: docs/boot.txt
================================================
Usage: torrent-docker boot [torrent-file] [container-name]

  Starts a new docker containers based on the image shared by
  the torrent file. The container state will be stored in ./container-name
  and the union file system mounted in ./container-name/mnt

  --peer,-p      to force connect to a peer
  --tracker,-t   to add additional trackers
  --net          to set the docker --net option
  --no-docker    do not boot the docker container - only mount the filesystem
  --nomount      do not mount this folder from the torrent
  --env,-e       set an env var NAME=VALUE

  You can attach to the debug log by following the instruction printed out when
  booting this container (usualy nc localhost 10000)


================================================
FILE: docs/create.txt
================================================
Usage: torrent-docker create [image-name]

  Creates a new torrent from an docker image.
  The torrent file will be saved in ./image-name.torrent
  and the torrent content will be stored in ./image-name/

  --announce-list, -a  List of trackers for the torrent (comma separated)


================================================
FILE: docs/destroy.txt
================================================
Usage: torrent-docker destroy [container-name]

  Completely removes a local container


================================================
FILE: docs/seed.txt
================================================
Usage: torrent-docker seed [torrent-file]

  Seeds a docker image torrent.
  You can also seed the torrent using your favorite torrent client

  --peer,-p      to force connect to a peer
  --tracker,-t   to add additional trackers


================================================
FILE: docs/tracker.txt
================================================
Usage: torrent-docker tracker

  Start a torrent tracker. Pass an address to this tracker
  to the seed and boot command using --tracker [addr]

  --port, -p   Port to listen on. Defaults to 80

================================================
FILE: filesystem.js
================================================
var fuse = require('fuse-bindings')
var fs = require('fs')
var collect = require('stream-collector')
var p = require('path')
var os = require('os')
var pump = require('pump')
var cuid = require('cuid')
var mkdirp = require('mkdirp')
var lexint = require('lexicographic-integer')
var level = require('level')
var tar = require('tar-fs')
var zlib = require('zlib')
var shasum = require('shasum')
var stream = require('stream')

var ENOENT = -2
var EPERM = -1
var EINVAL = -22

var toIndexKey = function(name) {
  var depth = name.split('/').length-1
  return lexint.pack(depth, 'hex')+name
}

var empty = function() {
  var p = new stream.PassThrough()
  p.end()
  return p
}

module.exports = function(mnt, container, opts, cb) {
  if (typeof opts === 'function') return module.exports(mnt, container, null, opts)
  if (!opts) opts = {}

  var dmode = 0
  var fmode = 0
  var log = opts.log || function() {}

  if (opts.readable) {
    dmode |= 0555
    fmode |= 0444
  }
  if (opts.writable) {
    dmode |= 0333
    fmode |= 0222
  }

  var handlers = {}
  var store = container

  var createImageStream = opts.createImageStream || empty
  var createIndexStream = opts.createIndexStream || empty

  var createReadStream = function(entry, offset) {
    var end = entry.start + entry.size - 1
    var start = entry.start+offset

    if (end < start) return empty()
    if (!entry.size) return empty()

    return createImageStream({start:start, end:end})
  }

  var ready = function() {
    var db = level(p.join(store, 'db'))

    var get = function(path, cb) {
      if (path === '/') return cb(null, {name:'/', mode: 0755, type:'directory'})

      db.get(toIndexKey(path), {valueEncoding:'json'}, function(err, entry) {
        if (err) return cb(err)
        if (entry.type === 'symlink') return get(p.resolve(p.dirname(path), entry.linkname), cb)
        if (!entry.layer) return cb(null, entry)
        fs.stat(entry.layer, function(err, stat) {
          entry.size = stat ? stat.size : 0
          cb(null, entry)
        })
      })
    }

    handlers.getattr = function(path, cb) {
      log('getattr', path)

      get(path, function(err, entry) {
        if (err) return cb(ENOENT)

        var stat = {}

        if (opts.uid !== undefined) stat.uid = opts.uid
        if (opts.gid !== undefined) stat.gid = opts.gid

        if (entry.type === 'file') {
          stat.size = entry.size
          stat.mode = 0100000 | entry.mode | fmode
          return cb(0, stat)
        }

        stat.size = 4096
        stat.mode = 040000 | entry.mode | dmode

        return cb(0, stat)
      })
    }

    handlers.readdir = function(path, cb) {
      log('readdir', path)

      if (!/\/$/.test(path)) path += '/'

      var prefix = toIndexKey(path)
      var rs = db.createReadStream({
        gte: prefix,
        lt: prefix+'\xff',
        valueEncoding: 'json'
      })

      collect(rs, function(err, entries) {
        if (err) return cb(ENOENT)

        var files = entries.map(function(entry) {
          return p.basename(entry.value.name)
        })

        cb(0, files)
      })
    }

    var files = []

    var toFlag = function(flags) {
      flags = flags & 3
      if (flags === 0) return 'r'
      if (flags === 1) return 'w'
      return 'r+'
    }

    var open = function(path, flags, cb) {
      var push = function(data) {
        var list = files[path] = files[path] || [true, true, true] // fd > 3
        var fd = list.indexOf(null)
        if (fd === -1) fd = list.length
        list[fd] = data
        cb(0, fd)
      }

      get(path, function(err, entry) {
        if (err) return cb(ENOENT)
        if (entry.type !== 'file') return cb(EINVAL)

        if (!entry.layer) return push({offset:0, entry:entry})

        fs.open(entry.layer, toFlag(flags), function(err, fd) {
          if (err) return cb(EPERM)
          push({fd:fd, entry:entry})
        })
      })
    }

    var copyOnWrite = function(path, mode, upsert, cb) {
      log('copy-on-write', path)

      var target = p.join(store, 'layer', shasum(path+'-'+Date.now()))

      var done = function(entry) {
        db.put(toIndexKey(entry.name), entry, {valueEncoding:'json'}, function(err) {
          if (err) return cb(EPERM)
          cb(0)
        })
      }

      var create = function() {
        var entry = {name:path, size:0, type:'file', mode:mode, layer:target}
        fs.writeFile(target, '', function(err) {
          if (err) return cb(EPERM)
          done(entry)
        })
      }

      get(path, function(err, entry) {
        if (entry && entry.layer) return cb(0)

        if (!entry && upsert) return create()
        if (!entry) return cb(ENOENT)

        entry.layer = target
        if (mode) entry.mode = mode

        pump(createReadStream(entry, 0), fs.createWriteStream(target), function(err) {
          if (err) return cb(EPERM)
          done(entry)
        })
      })
    }

    handlers.open = function(path, flags, cb) {
      log('open', path, flags)

      if (flags === 0) return open(path, flags, cb)
      copyOnWrite(path, 0, false, function(err) {
        if (err) return cb(err)
        open(path, flags, cb)
      })
    }

    handlers.release = function(path, handle, cb) {
      log('release', path, handle)

      var list = files[path] || []
      var file = list[handle]
      if (!file) return cb(ENOENT)

      if (file.stream) file.stream.destroy()
      list[handle] = null
      if (!list.length) delete files[path]

      if (file.fd === undefined) return cb(0)

      fs.close(file.fd, function(err) {
        if (err) return cb(EPERM)
        cb(0)
      })
    }

    handlers.read = function(path, handle, buf, len, offset, cb) {
      log('read', path, offset, len, handle)

      var list = files[path] || []
      var file = list[handle]
      if (!file) return cb(ENOENT)

      if (len + offset > file.entry.size) len = file.entry.size - offset;

      if (file.fd !== undefined) {
        fs.read(file.fd, buf, 0, len, offset, function(err, bytes) {
          if (err) return cb(EPERM)
          cb(bytes)
        })
        return
      }

      if (file.stream && file.offset !== offset) {
        file.stream.destroy()
        file.stream = null
      }

      if (!file.stream) {
        file.stream = createReadStream(file.entry, offset)
        file.offset = offset
      }

      var loop = function() {
        var result = file.stream.read(len)
        if (!result) return file.stream.once('readable', loop)
        file.offset += len
        result.copy(buf)
        cb(result.length)
      }

      loop()
    }

    handlers.truncate = function(path, size, cb) {
      log('truncate', path, size)

      copyOnWrite(path, 0, false, function(err) {
        if (err) return cb(err)
        get(path, function(err, entry) {
          if (err || !entry.layer) return cb(EPERM)
          fs.truncate(entry.layer, size, function(err) {
            if (err) return cb(EPERM)
            cb(0)
          })
        })
      })
    }

    handlers.write = function(path, handle, buf, len, offset, cb) {
      log('write', path, offset, len, handle)

      var list = files[path] || []
      var file = list[handle]
      if (!file) return cb(ENOENT)
      if (file.fd === undefined) {
        return cb(EPERM)
      }

      fs.write(file.fd, buf, 0, len, offset, function(err, bytes) {
        if (err) return cb(EPERM)
        cb(bytes)
      })
    }

    handlers.unlink = function(path, cb) {
      log('unlink', path)

      get(path, function(err, entry) {
        if (!entry) return cb(ENOENT)
        db.del(toIndexKey(path), function() {
          if (!entry.layer) return cb(0)
          fs.unlink(p.join(store, 'layer', entry.layer), function() {
            cb(0)
          })
        })
      })
    }

    handlers.rename = function(src, dst, cb) {
      log('rename', src, dst)

      copyOnWrite(src, 0, false, function(err) {
        if (err) return cb(err)
        get(src, function(err, entry) {
          if (err || !entry.layer) return cb(EPERM)
          var batch = [{type:'del', key:toIndexKey(entry.name)}, {type:'put', key:toIndexKey(dst), valueEncoding:'json', value:entry}]
          entry.name = dst
          db.batch(batch, function(err) {
            if (err) return cb(EPERM)
            cb(0)
          })
        })
      })
    }

    handlers.mkdir = function(path, mode, cb) {
      log('mkdir', path)

      db.put(toIndexKey(path), {name:path, mode:mode, type:'directory', size:0}, {valueEncoding:'json'}, function(err) {
        if (err) return cb(EPERM)
        cb(0)
      })
    }

    handlers.rmdir = function(path, cb) {
      log('rmdir', path)

      handlers.readdir(path, function(err, list) {
        if (err) return cb(EPERM)
        if (list.length) return cb(EPERM)
        handlers.unlink(path, cb)
      })
    }

    handlers.chown = function() {
      console.error('chown is not implemented')
    }

    handlers.chmod = function(path, mode, cb) {
      log('chmod', path, mode)

      get(path, function(err, entry) {
        if (err) return cb(err)
        entry.mode = mode
        db.put(toIndexKey(path), entry, {valueEncoding:'json'}, function(err) {
          if (err) return cb(EPERM)
          cb(0)
        })
      })
    }

    handlers.create = function(path, mode, cb) {
      log('create', path, mode)

      copyOnWrite(path, mode, true, function(err) {
        if (err) return cb(err)
        open(path, 1, cb)
      })
    }

    handlers.getxattr = function(path, name, buffer, length, offset, cb) {
      log('getxattr')

      cb(EPERM)
    }

    handlers.setxattr = function(path, name, buffer, length, offset, flags, cb) {
      log('setxattr')

      cb(0)
    }

    handlers.statfs = function(path, cb) {
      cb(0, {
        bsize: 1000000,
        frsize: 1000000,
        blocks: 1000000,
        bfree: 1000000,
        bavail: 1000000,
        files: 1000000,
        ffree: 1000000,
        favail: 1000000,
        fsid: 1000000,
        flag: 1000000,
        namemax: 1000000
      })
    }

    handlers.destroy = function(cb) {
      cb()
    }

    fuse.mount(mnt, handlers, function (err) {
      if (err) return cb(err)
      cb(null, handlers)
    })
  }

  fs.exists(p.join(store, 'db'), function(exists) {
    if (exists) return fuse.unmount(mnt, ready)

    mkdirp(p.join(store, 'layer'), function() {
      pump(
        createIndexStream(),
        zlib.createGunzip(),
        tar.extract(p.join(store, 'db')),
        function() {
          fuse.unmount(mnt, ready)
        }
      )
    })
  })
}

================================================
FILE: help.txt
================================================
Usage: torrent-docker [cmd]

Available commands are

  create    create a new docker torrent
  seed      seed a docker torrent
  boot      boot a container from a torrent
  destroy   destroy a container
  tracker   start a tracker

Add --help after any command for detailed help


================================================
FILE: package.json
================================================
{
  "name": "torrent-docker",
  "version": "1.6.0",
  "description": "MAD SCIENCE realtime boot of remote docker images using bittorrent",
  "main": "bin.js",
  "bin": {
    "torrent-docker": "./bin.js"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "dependencies": {
    "bittorrent-tracker": "^2.7.0",
    "create-torrent": "^3.2.0",
    "cuid": "^1.2.4",
    "docker-remote-api": "^4.4.0",
    "docker-run": "^2.0.0",
    "duplexify": "^3.2.0",
    "freeport": "^1.0.3",
    "fuse-bindings": "^2.1.2",
    "level": "^0.18.0",
    "lexicographic-integer": "^1.1.0",
    "minimist": "^1.1.0",
    "mkdirp": "^0.5.0",
    "pretty-bytes": "^1.0.1",
    "pump": "^1.0.0",
    "rimraf": "^2.2.8",
    "shasum": "^1.0.0",
    "stream-collector": "^1.0.1",
    "tar-fs": "^1.2.0",
    "tar-stream": "^1.1.1",
    "through2": "^0.6.3",
    "torrent-stream": "^0.18.1"
  },
  "devDependencies": {},
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/torrent-docker.git"
  },
  "bugs": {
    "url": "https://github.com/mafintosh/torrent-docker/issues"
  },
  "homepage": "https://github.com/mafintosh/torrent-docker"
}
Download .txt
gitextract_ct1_8s_w/

├── .gitignore
├── LICENSE
├── README.md
├── bin/
│   ├── boot.js
│   ├── create.js
│   ├── destroy.js
│   ├── seed.js
│   └── tracker.js
├── bin.js
├── docs/
│   ├── boot.txt
│   ├── create.txt
│   ├── destroy.txt
│   ├── seed.txt
│   └── tracker.txt
├── filesystem.js
├── help.txt
└── package.json
Condensed preview — 17 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (29K chars).
[
  {
    "path": ".gitignore",
    "chars": 92,
    "preview": "image.tar\nimage.db\nimage.db.tgz\nindex.tgz\nnode_modules\nmnt\ncontainer\n*.torrent\ncontainers/*\n"
  },
  {
    "path": "LICENSE",
    "chars": 1079,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2014 Mathias Buus\n\nPermission is hereby granted, free of charge, to any person obta"
  },
  {
    "path": "README.md",
    "chars": 2698,
    "preview": "# torrent-docker\n\nMAD SCIENCE realtime boot of remote docker images using bittorrent\n\n```\nnpm install -g torrent-docker\n"
  },
  {
    "path": "bin/boot.js",
    "chars": 4038,
    "preview": "#!/usr/bin/env node\n\nvar torrents = require('torrent-stream')\nvar filesystem = require('../filesystem')\nvar mkdirp = req"
  },
  {
    "path": "bin/create.js",
    "chars": 3001,
    "preview": "#!/usr/bin/env node\n\nvar docker = require('docker-remote-api')\nvar zlib = require('zlib')\nvar level = require('level')\nv"
  },
  {
    "path": "bin/destroy.js",
    "chars": 396,
    "preview": "#!/usr/bin/env node\n\nvar fuse = require('fuse-bindings')\nvar rimraf = require('rimraf')\nvar fs = require('fs')\nvar minim"
  },
  {
    "path": "bin/seed.js",
    "chars": 1069,
    "preview": "#!/usr/bin/env node\n\nvar torrents = require('torrent-stream')\nvar pretty = require('pretty-bytes')\nvar path = require('p"
  },
  {
    "path": "bin/tracker.js",
    "chars": 1036,
    "preview": "#!/usr/bin/env node\n\nvar minimist = require('minimist')\nvar tracker = require('bittorrent-tracker/server')\nvar fs = requ"
  },
  {
    "path": "bin.js",
    "chars": 426,
    "preview": "#!/usr/bin/env node\n\nvar cmd = process.argv[2]\nvar fs = require('fs')\n\nprocess.argv.splice(2, 1)\n\nif (cmd === 'seed') re"
  },
  {
    "path": "docs/boot.txt",
    "chars": 708,
    "preview": "Usage: torrent-docker boot [torrent-file] [container-name]\n\n  Starts a new docker containers based on the image shared b"
  },
  {
    "path": "docs/create.txt",
    "chars": 279,
    "preview": "Usage: torrent-docker create [image-name]\n\n  Creates a new torrent from an docker image.\n  The torrent file will be save"
  },
  {
    "path": "docs/destroy.txt",
    "chars": 87,
    "preview": "Usage: torrent-docker destroy [container-name]\n\n  Completely removes a local container\n"
  },
  {
    "path": "docs/seed.txt",
    "chars": 231,
    "preview": "Usage: torrent-docker seed [torrent-file]\n\n  Seeds a docker image torrent.\n  You can also seed the torrent using your fa"
  },
  {
    "path": "docs/tracker.txt",
    "chars": 193,
    "preview": "Usage: torrent-docker tracker\n\n  Start a torrent tracker. Pass an address to this tracker\n  to the seed and boot command"
  },
  {
    "path": "filesystem.js",
    "chars": 10589,
    "preview": "var fuse = require('fuse-bindings')\nvar fs = require('fs')\nvar collect = require('stream-collector')\nvar p = require('pa"
  },
  {
    "path": "help.txt",
    "chars": 279,
    "preview": "Usage: torrent-docker [cmd]\n\nAvailable commands are\n\n  create    create a new docker torrent\n  seed      seed a docker t"
  },
  {
    "path": "package.json",
    "chars": 1159,
    "preview": "{\n  \"name\": \"torrent-docker\",\n  \"version\": \"1.6.0\",\n  \"description\": \"MAD SCIENCE realtime boot of remote docker images "
  }
]

About this extraction

This page contains the full source code of the mafintosh/torrent-docker GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 17 files (26.7 KB), approximately 7.6k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!