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!

## 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"
}
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.