Files: 2b44276bef31490ce989ab1cc7a1943c8bac0d4e / bootstrap.js
7825 bytesRaw
1 | var http = require('http') |
2 | var os = require('os') |
3 | var fs = require('fs') |
4 | var path = require('path') |
5 | var URL = require('url') |
6 | var http = require('http') |
7 | var https = require('https') |
8 | var crypto = require('crypto') |
9 | var Transform = require('stream').Transform |
10 | var proc = require('child_process') |
11 | |
12 | function 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 | |
22 | var ssbAppname = process.env.ssb_appname || 'ssb' |
23 | var ssbPath = process.env.ssb_path || path.join(os.homedir(), '.' + ssbAppname) |
24 | var blobsPath = path.join(ssbPath, 'blobs') |
25 | var blobsTmpPath = path.join(blobsPath, 'tmp') |
26 | |
27 | var numTmpBlobs = 0 |
28 | var remotes = [] |
29 | |
30 | function blobFilename(buf) { |
31 | var str = buf.toString('hex') |
32 | return path.join(blobsPath, 'sha256', str.slice(0, 2), str.slice(2)) |
33 | } |
34 | |
35 | function 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 | |
41 | function 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 | |
49 | function 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 | |
58 | function serveStatus(res, code, message, body) { |
59 | res.writeHead(code, message) |
60 | res.end(body || message) |
61 | } |
62 | |
63 | function 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 | |
73 | function 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 | |
83 | function 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 | |
93 | function 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 | |
99 | function 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 | |
112 | function 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 | |
119 | function 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 | |
167 | function 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 | |
177 | function 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 | |
191 | process.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.push('--registry=http://' + regHostname + '/') |
222 | args.push('--//' + regHostname + '/:_authToken=1') |
223 | } |
224 | var child = proc.spawn(cmd, args, { |
225 | stdio: 'inherit' |
226 | }) |
227 | child.on('exit', process.exit) |
228 | process.on('SIGINT', function () { child.kill('SIGINT') }) |
229 | process.on('SIGTERM', function () { child.kill('SIGTERM') }) |
230 | process.on('uncaughtException', function (e) { |
231 | console.error(e) |
232 | child.kill('SIGKILL') |
233 | process.exit(1) |
234 | }) |
235 | } |
236 | }) |
237 | }) |
238 |
Built with git-ssb-web