git ssb

3+

cel / ssb-npm-registry



Tree: 902a7cb045aeedf56a1dab29c2975cd3b475937d

Files: 902a7cb045aeedf56a1dab29c2975cd3b475937d / bootstrap.js

7970 bytesRaw
1var http = require('http')
2var os = require('os')
3var fs = require('fs')
4var path = require('path')
5var URL = require('url')
6var http = require('http')
7var https = require('https')
8var crypto = require('crypto')
9var Transform = require('stream').Transform
10var proc = require('child_process')
11
12function usage(code) {
13 console.error('Usage: ' + process.argv[0] + ' ' + process.argv[1] + '\n' +
14 ' [--help] [--verbose]\n' +
15 ' [--blobs-host <host>] [--blobs-port <port>]\n' +
16 ' [--registry-host <host>] [--registry-port <port>]\n' +
17 ' [--blobs-remote <remote_address>]\n' +
18 ' [--] [<cmd> <args...>]')
19 process.exit(code)
20}
21
22var ssbAppname = process.env.ssb_appname || 'ssb'
23var ssbPath = process.env.ssb_path || path.join(os.homedir(), '.' + ssbAppname)
24var blobsPath = path.join(ssbPath, 'blobs')
25var blobsTmpPath = path.join(blobsPath, 'tmp')
26
27var numTmpBlobs = 0
28var remotes = []
29
30function blobFilename(buf) {
31 var str = buf.toString('hex')
32 return path.join(blobsPath, 'sha256', str.slice(0, 2), str.slice(2))
33}
34
35function formatHttpAddr(addr) {
36 return 'http://' + (typeof addr === 'string' ? 'unix:' + addr
37 : addr.family === 'IPv6' ? '[' + addr.address + ']:' + addr.port
38 : addr.address + ':' + addr.port)
39}
40
41function serveBlobs(opts, req, res) {
42 var p = URL.parse(req.url)
43 if (p.pathname === '/') return serveStatus(res, 204)
44 var m = /^\/blobs\/get\/(&([A-Za-z0-9\/+]{43}=)\.sha256)$/.exec(p.pathname)
45 if (m) return serveBlobsGet(req, res, opts.remote, m[1], new Buffer(m[2], 'base64'))
46 return serveStatus(res, 404, 'Not Found')
47}
48
49function serveRegistry(opts, req, res) {
50 var p = URL.parse(req.url)
51 var pathname = req.url.replace(/\?.*/, '')
52 if (pathname === '/') return serveStatus(res, 204)
53 if (pathname === '/-/ping') return serveStatus(res, 200, null, '"pong"')
54 if (/^\/-\//.test(pathname)) return serveStatus(404)
55 return servePkg(opts.pkgs, req, res, pathname.substr(1))
56}
57
58function serveStatus(res, code, message, body) {
59 res.writeHead(code, message)
60 res.end(body || message)
61}
62
63function serveStop(opts, req, res) {
64 var addr = req.socket.remoteAddress
65 if (addr !== '::1' && addr !== '127.0.0.1' && addr !== '::ffff:127.0.0.1') {
66 return serveStatus(res, 403)
67 }
68 serveStatus(res, 200)
69 opts.registryServer.close()
70 opts.blobsServer.close()
71}
72
73function serveBlobsGet(req, res, remote, id, hash) {
74 var filename = blobFilename(hash)
75 getAddBlob(remote, id, hash, filename, function (err, stream) {
76 if (err) return serveStatus(res, 500, null, err.message)
77 if (!stream) return serveStatus(res, 404, 'Blob Not Found')
78 res.writeHead(200)
79 stream.pipe(res)
80 })
81}
82
83function getAddBlob(remote, id, hash, filename, cb) {
84 fs.access(filename, fs.constants.R_OK, function (err) {
85 if (err && err.code === 'ENOENT') {
86 return fetchAddBlob(remote, id, hash, filename, cb)
87 }
88 if (err) return cb(err)
89 cb(null, fs.createReadStream(filename))
90 })
91}
92
93function getRemoteBlob(remote, id, cb) {
94 if (/^https:\/\//.test(remote)) return https.get(remote + id, cb)
95 if (/^http:\/\//.test(remote)) return https.get(remote + id, cb)
96 return http.get('http://' + remote + '/blobs/get/' + id, cb)
97}
98
99function mkdirp(dir, cb) {
100 fs.stat(dir, function (err, stats) {
101 if (!err) return cb()
102 fs.mkdir(dir, function (err) {
103 if (!err) return cb()
104 mkdirp(path.dirname(dir), function (err) {
105 if (err) return cb(err)
106 fs.mkdir(dir, cb)
107 })
108 })
109 })
110}
111
112function rename(src, dest, cb) {
113 mkdirp(path.dirname(dest), function (err) {
114 if (err) return cb(err)
115 fs.rename(src, dest, cb)
116 })
117}
118
119function fetchAddBlob(remote, id, hash, filename, cb) {
120 var req = getRemoteBlob(remote, id, function (res) {
121 req.removeListener('error', cb)
122 if (res.statusCode !== 200) return cb(new Error(res.statusMessage))
123 mkdirp(blobsTmpPath, function (err) {
124 if (err) return res.destroy(), cb(err)
125 var blobTmpPath = path.join(blobsTmpPath, Date.now() + '-' + numTmpBlobs++)
126 fs.open(blobTmpPath, 'w+', function (err, fd) {
127 if (err) return res.destroy(), cb(err)
128 var writeStream = fs.createWriteStream(null, {
129 fd: fd, flags: 'w+', autoClose: false})
130 var hasher = crypto.createHash('sha256')
131 var hashThrough = new Transform({
132 transform: function (data, encoding, cb) {
133 hasher.update(data)
134 cb(null, data)
135 }
136 })
137 res.pipe(hashThrough).pipe(writeStream, {end: false})
138 res.on('error', function (err) {
139 writeStream.end(function (err1) {
140 fs.unlink(blobTmpPath, function (err2) {
141 cb(err || err1 || err2)
142 })
143 })
144 })
145 hashThrough.on('end', function () {
146 var receivedHash = hasher.digest()
147 if (hash.compare(receivedHash)) {
148 writeStream.end(function (err) {
149 fs.unlink(blobTmpPath, function (err1) {
150 cb(err || err1)
151 })
152 })
153 } else {
154 res.unpipe(hashThrough)
155 rename(blobTmpPath, filename, function (err) {
156 if (err) return console.error(err)
157 cb(null, fs.createReadStream(null, {fd: fd, start: 0}))
158 })
159 }
160 })
161 })
162 })
163 })
164 req.on('error', cb)
165}
166
167function servePkg(pkgs, req, res, pathname) {
168 var self = this
169 var parts = pathname.split('/')
170 var pkgName = parts.shift()
171 if (parts[0] === '-rev') return serveStatus(res, 501, 'Unpublish is not supported')
172 if (parts.length > 0) return serveStatus(res, 404)
173 var pkg = pkgs[pkgName] || {}
174 serveStatus(res, 200, null, JSON.stringify(pkg, 0, 2))
175}
176
177function startServers(opts, cb) {
178 var waiting = 2
179 opts.blobsServer = http.createServer(serveBlobs.bind(null, opts))
180 .listen(opts.blobsPort, opts.blobsHost, function () {
181 if (opts.verbose) console.error('blobs listening on ' + formatHttpAddr(this.address()))
182 if (!--waiting) cb()
183 })
184 opts.registryServer = http.createServer(serveRegistry.bind(null, opts))
185 .listen(opts.registryPort, opts.registryHost, function () {
186 if (opts.verbose) console.error('registry listening on ' + formatHttpAddr(this.address()))
187 if (!--waiting) cb()
188 })
189}
190
191process.nextTick(function () {
192 var args = process.argv.slice(2)
193 var opts = {
194 pkgs: exports.pkgs || {},
195 blobsPort: 8989,
196 registryPort: 8043,
197 }
198 var cmd
199
200 getopts: while (args.length) {
201 var arg = args.shift()
202 switch (arg) {
203 case '--help': return usage(0)
204 case '--verbose': opts.verbose = true; break
205 case '--blobs-host': opts.blobsHost = args.shift(); break
206 case '--blobs-port': opts.blobsPort = args.shift(); break
207 case '--registry-port': opts.registryPort = args.shift(); break
208 case '--registry-host': opts.registryHost = args.shift(); break
209 case '--blobs-remote': opts.remote = args.shift(); break
210 case '--': cmd = args.shift(); break getopts
211 default: if (/^--/.test(arg)) return usage(1); cmd = arg; break getopts
212 }
213 }
214
215 startServers(opts, function (err) {
216 if (err) throw err
217 if (cmd) {
218 var regHost = opts.registryHost || 'localhost'
219 var regHostname = regHost + ':' + opts.registryPort
220 if (cmd === 'npm' || cmd === 'npx') {
221 args.unshift('--registry=http://' + regHostname + '/')
222 args.unshift('--//' + regHostname + '/:_authToken=1')
223 args.unshift('--download=http://' + regHostname + '/-/prebuild/{name}-v{version}-{runtime}-v{abi}-{platform}{libc}-{arch}.tar.gz')
224 }
225 var child = proc.spawn(cmd, args, {
226 stdio: 'inherit'
227 })
228 child.on('exit', process.exit)
229 process.on('SIGINT', function () { child.kill('SIGINT') })
230 process.on('SIGTERM', function () { child.kill('SIGTERM') })
231 process.on('uncaughtException', function (e) {
232 console.error(e)
233 child.kill('SIGKILL')
234 process.exit(1)
235 })
236 }
237 })
238})
239

Built with git-ssb-web