git ssb

3+

cel / ssb-npm-registry



Commit 1570ad9df58d43e3838545d51222ead7e6503c5c

Add new bootstrap script

cel committed on 6/30/2018, 8:57:26 PM
Parent: a3a138c8aab9a07782fdb33a880803a50d51b994

Files changed

package-lock.jsonchanged
package.jsonchanged
bootstrap/bin.jsadded
bootstrap/usage.txtadded
package-lock.jsonView
@@ -72,8 +72,16 @@
7272 "version": "1.1.11",
7373 "resolved": "http://localhost:8989/blobs/get/&+uVE8RHNwJIJa68sQGICGbxnCTCOdLLhSt/Pb4NmgL0=.sha256",
7474 "integrity": "sha256-+uVE8RHNwJIJa68sQGICGbxnCTCOdLLhSt/Pb4NmgL0="
7575 },
76 + "pull-file": {
77 + "version": "1.1.0",
78 + "resolved": "https://registry.npmjs.org/pull-file/-/pull-file-1.1.0.tgz",
79 + "integrity": "sha1-HdmHYF1jV6DSPB5Lgm95FaIVEpw=",
80 + "requires": {
81 + "pull-utf8-decoder": "1.0.2"
82 + }
83 + },
7684 "pull-hash": {
7785 "version": "1.0.0",
7886 "resolved": "http://localhost:8989/blobs/get/&mVOLtd5e8OPAoi/ARUMfEkgZlHrlkPzgapIlv2a9FwI=.sha256",
7987 "integrity": "sha256-mVOLtd5e8OPAoi/ARUMfEkgZlHrlkPzgapIlv2a9FwI="
@@ -82,8 +90,13 @@
8290 "version": "3.6.1",
8391 "resolved": "http://localhost:8989/blobs/get/&xEhoJll+9Z5EYr7s7MUgCbBhdF1nekcqnIdIKV4z2SU=.sha256",
8492 "integrity": "sha256-xEhoJll+9Z5EYr7s7MUgCbBhdF1nekcqnIdIKV4z2SU="
8593 },
94 + "pull-utf8-decoder": {
95 + "version": "1.0.2",
96 + "resolved": "https://registry.npmjs.org/pull-utf8-decoder/-/pull-utf8-decoder-1.0.2.tgz",
97 + "integrity": "sha1-p6+iOE0eZBWl1gIFQSbMjeO8vOc="
98 + },
8699 "readable-stream": {
87100 "version": "2.3.3",
88101 "resolved": "http://localhost:8989/blobs/get/&jHa+qo0s1a6eHgzbswuggi5CrRuDVnqJJkt+ePt6hFE=.sha256",
89102 "integrity": "sha256-jHa+qo0s1a6eHgzbswuggi5CrRuDVnqJJkt+ePt6hFE=",
package.jsonView
@@ -13,8 +13,9 @@
1313 "asyncmemo": "^1.1.0",
1414 "hashlru": "^2.2.1",
1515 "multicb": "^1.2.2",
1616 "pull-cat": "^1.1.11",
17 + "pull-file": "^1.1.0",
1718 "pull-hash": "^1.0.0",
1819 "pull-stream": "^3.6.1",
1920 "semver": "^5.4.1",
2021 "stream-to-pull-stream": "^1.7.2",
bootstrap/bin.jsView
@@ -1,0 +1,387 @@
1 +#!/usr/bin/env node
2 +
3 +var http = require('http')
4 +var fs = require('fs')
5 +var proc = require('child_process')
6 +var path = require('path')
7 +var URL = require('url')
8 +var http = require('http')
9 +var https = require('https')
10 +var crypto = require('crypto')
11 +var Transform = require('stream').Transform
12 +var SsbNpmRegistry = require('../')
13 +var pullFile = require('pull-file')
14 +var lru = require('hashlru')
15 +var memo = require('asyncmemo')
16 +
17 +var ssbAppname = process.env.ssb_appname || 'ssb'
18 +var ssbPath = process.env.ssb_path || path.join(process.env.HOME, '.' + ssbAppname)
19 +var blobsPath = path.join(ssbPath, 'blobs')
20 +var configPath = path.join(ssbPath, 'config')
21 +var blobsTmpPath = path.join(blobsPath, 'tmp')
22 +var numTmpBlobs = 0
23 +
24 +var host = null
25 +var port = null
26 +var msgsUrl = null
27 +var blobsUrl = null
28 +var server
29 +var listenHostname
30 +var msgIds = []
31 +
32 +function shift(args) {
33 + if (!args.length) return usage(1)
34 + return args.shift()
35 +}
36 +
37 +function version() {
38 + var pkg = require('./package')
39 + console.log(pkg.name + ' ' + pkg.version)
40 +}
41 +
42 +function usage(code) {
43 + console.error(fs.readFileSync(path.join(__dirname, 'usage.txt')))
44 + process.exit(code)
45 +}
46 +
47 +function main(args) {
48 + var viewerUrl = null
49 + var wsUrl = null
50 +
51 + var cmdArgs = []
52 + while (args.length) {
53 + var arg = args.shift()
54 + switch (arg) {
55 + case '--help': return usage(0)
56 + case '--version': return version()
57 + case '--host': host = shift(args); break
58 + case '--port': port = shift(args); break
59 + case '--msgs-url': msgsUrl = shift(args); break
60 + case '--blobs-url': blobsUrl = shift(args); break
61 + case '--ws-url': wsUrl = shift(args); break
62 + case '--viewer-url': viewerUrl = shift(args); break
63 + case '--msg': msgIds.push(shift(args)); break
64 + case '--': cmdArgs.push.apply(cmdArgs, args.splice(0)); break
65 + default: cmdArgs.push(arg); break
66 + }
67 + }
68 +
69 + if (wsUrl && viewerUrl) {
70 + throw '--ws-url and --viewer-url options conflict'
71 + }
72 + if (wsUrl && (msgsUrl || blobsUrl)) {
73 + throw '--ws-url option conflicts with --msgs-url and --blobs-url'
74 + }
75 + if (viewerUrl && (msgsUrl || blobsUrl)) {
76 + throw '--viewer-url option conflicts with --msgs-url and --blobs-url'
77 + }
78 +
79 + if (wsUrl) {
80 + wsUrl = wsUrl.replace(/\/+$/, '')
81 + blobsUrl = wsUrl + '/blobs/get/%s'
82 + msgsUrl = wsUrl + '/msg/%s'
83 + }
84 +
85 + if (viewerUrl) {
86 + viewerUrl = viewerUrl.replace(/\/+$/, '')
87 + blobsUrl = viewerUrl + '/%s'
88 + msgsUrl = viewerUrl + '/%s.json'
89 + }
90 +
91 + if (!port && !cmdArgs.length) {
92 + port = 8989
93 + }
94 +
95 + server = http.createServer(serve)
96 + server.listen(port, host, function () {
97 + if (cmdArgs.length) {
98 + var cmd = cmdArgs.shift()
99 + port = server.address().port
100 + var registryUrl = 'http://' + (host || 'localhost') + ':' + port
101 + + '/npm/' + msgIds.map(encodeURIComponent).join(',') + '/'
102 + var env = {}
103 + for (var k in process.env) env[k] = process.env[k]
104 + env.npm_config_registry = registryUrl
105 + if (cmd === 'npm') {
106 + cmdArgs.unshift('--no-update-notifier')
107 + cmdArgs.unshift('--fetch-retries=0')
108 + cmdArgs.unshift('--download=' + registryUrl + '-/prebuild/{name}-v{version}-{runtime}-v{abi}-{platform}{libc}-{arch}.tar.gz')
109 + }
110 + cmdArgs.forEach(function (arg, i) {
111 + cmdArgs[i] = arg.replace(/\{registry\}/g, registryUrl)
112 + })
113 + var child = proc.spawn(cmd, cmdArgs, {
114 + env: env,
115 + stdio: 'inherit'
116 + })
117 + child.on('exit', process.exit)
118 + process.on('SIGINT', function () { child.kill('SIGINT') })
119 + process.on('SIGTERM', function () { child.kill('SIGTERM') })
120 + process.on('uncaughtException', function (e) {
121 + console.error(e)
122 + child.kill('SIGKILL')
123 + process.exit(1)
124 + })
125 + } else {
126 + printServerListening()
127 + }
128 + })
129 +}
130 +
131 +function printServerListening() {
132 + var addr = server.address()
133 + listenHostname = typeof addr === 'string' ? 'unix:' + addr
134 + : addr.family === 'IPv6' ? '[' + addr.address + ']:' + addr.port
135 + : addr.address + ':' + addr.port
136 + console.log('Listening on http://' + listenHostname)
137 +}
138 +
139 +function serveStatus(res, code, message) {
140 + res.writeHead(code, message)
141 + res.end(message)
142 +}
143 +
144 +var blobIdRegex = /^&([A-Za-z0-9\/+]{43}=)\.sha256$/
145 +
146 +function idToBuf(id) {
147 + var m = blobIdRegex.exec(id)
148 + if (!m) return null
149 + return new Buffer(m[1], 'base64')
150 +}
151 +
152 +function blobFilename(buf) {
153 + if (!buf) return null
154 + var str = buf.toString('hex')
155 + return path.join(blobsPath, 'sha256', str.slice(0, 2), str.slice(2))
156 +}
157 +
158 +function getRemoteBlob(id, cb) {
159 + var url = blobsUrl.replace('%s', id)
160 + return /^https:\/\//.test(url) ? https.get(url, cb) : http.get(url, cb)
161 +}
162 +
163 +function getRemoteMsg(id, cb) {
164 + var url = msgsUrl.replace('%s', encodeURIComponent(id))
165 + return /^https:\/\//.test(url) ? https.get(url, cb) : http.get(url, cb)
166 +}
167 +
168 +function mkdirp(dir, cb) {
169 + fs.stat(dir, function (err, stats) {
170 + if (!err) return cb()
171 + fs.mkdir(dir, function (err) {
172 + if (!err) return cb()
173 + mkdirp(path.dirname(dir), function (err) {
174 + if (err) return cb(err)
175 + fs.mkdir(dir, cb)
176 + })
177 + })
178 + })
179 +}
180 +
181 +function rename(src, dest, cb) {
182 + mkdirp(path.dirname(dest), function (err) {
183 + if (err) return cb(err)
184 + fs.rename(src, dest, cb)
185 + })
186 +}
187 +
188 +function hash(arr) {
189 + return arr.reduce(function (hash, item) {
190 + return hash.update(String(item))
191 + }, crypto.createHash('sha256')).digest('base64')
192 +}
193 +
194 +function fetchAddBlob(id, hash, filename, opts, cb) {
195 + opts = opts || {}
196 + var readIt = opts.readIt !== false
197 + if (!blobsUrl) return cb(new Error('Missing blobs URL'))
198 + var req = getRemoteBlob(id, function (res) {
199 + req.removeListener('error', cb)
200 + if (res.statusCode !== 200) return cb(new Error(res.statusMessage))
201 + mkdirp(blobsTmpPath, function (err) {
202 + if (err) return res.destroy(), cb(err)
203 + var blobTmpPath = path.join(blobsTmpPath, Date.now() + '-' + numTmpBlobs++)
204 + fs.open(blobTmpPath, 'w+', function (err, fd) {
205 + if (err) return res.destroy(), cb(err)
206 + var writeStream = fs.createWriteStream(null, {
207 + fd: fd, flags: 'w+', autoClose: false})
208 + var hasher = crypto.createHash('sha256')
209 + var hashThrough = new Transform({
210 + transform: function (data, encoding, cb) {
211 + hasher.update(data)
212 + cb(null, data)
213 + }
214 + })
215 + res.pipe(hashThrough).pipe(writeStream, {end: false})
216 + res.on('error', function (err) {
217 + writeStream.end(function (err1) {
218 + fs.unlink(blobTmpPath, function (err2) {
219 + cb(err || err1 || err2)
220 + })
221 + })
222 + })
223 + hashThrough.on('end', function () {
224 + var receivedHash = hasher.digest()
225 + if (receivedHash.compare(hash)) {
226 + writeStream.end(function (err) {
227 + fs.unlink(blobTmpPath, function (err1) {
228 + cb(err1 || err || new Error('mismatched hash'))
229 + })
230 + })
231 + } else {
232 + res.unpipe(hashThrough)
233 + rename(blobTmpPath, filename, function (err) {
234 + if (err) return console.error(err)
235 + if (readIt) cb(null, fs.createReadStream(null, {fd: fd, start: 0}))
236 + else fs.close(fd, function (err) {
237 + if (err) return cb(err)
238 + cb(null)
239 + })
240 + })
241 + }
242 + })
243 + })
244 + })
245 + })
246 + req.on('error', cb)
247 +}
248 +
249 +function getAddBlob(id, hash, filename, cb) {
250 + fs.access(filename, fs.constants.R_OK, function (err) {
251 + if (err && err.code === 'ENOENT') return fetchAddBlob(id, hash, filename, {}, cb)
252 + if (err) return cb(err)
253 + cb(null, fs.createReadStream(filename))
254 + })
255 +}
256 +
257 +function serveBlobsGet(req, res, id) {
258 + try { id = decodeURIComponent(id) }
259 + catch (e) {}
260 + var hash = idToBuf(id)
261 + var filename = blobFilename(hash)
262 + getAddBlob(id, hash, filename, function (err, stream) {
263 + if (err) return serveStatus(res, 500, err.message)
264 + if (!stream) return serveStatus(res, 404, 'Blob Not Found')
265 + res.writeHead(200)
266 + stream.pipe(res)
267 + })
268 +}
269 +
270 +function serveMsg(req, res, id) {
271 + try { id = decodeURIComponent(id) }
272 + catch (e) {}
273 + ssbGet(id, function (err, value) {
274 + if (err) return serveStatus(res, 400, err.message)
275 + if (!msg) return serveStatus(res, 404, 'Msg Not Found')
276 + res.writeHead(200, {'Content-Type': 'application/json'})
277 + res.end(JSON.stringify({key: id, value: msg}))
278 + })
279 +}
280 +
281 +function whoami(cb) {
282 + cb(null, {id: 'foo'})
283 +}
284 +
285 +var ssbGet = memo({
286 + cache: lru(1000)
287 +}, function (id, cb) {
288 + if (!msgsUrl) return cb(new Error('Missing msgs URL'))
289 + var req = getRemoteMsg(id, function (res) {
290 + req.removeListener('error', cb)
291 + if (res.statusCode !== 200)
292 + return cb(new Error('unable to get msg ' + id + ': ' + res.statusMessage))
293 + var bufs = []
294 + res.on('data', function (buf) {
295 + bufs.push(buf)
296 + })
297 + res.on('error', onError)
298 + function onError(err) {
299 + cb(err)
300 + cb = null
301 + }
302 + res.on('end', function () {
303 + if (!cb) return
304 + res.removeListener('error', onError)
305 + var buf = Buffer.concat(bufs)
306 + try {
307 + var obj = JSON.parse(buf.toString('utf8'))
308 + if (Array.isArray(obj)) obj = obj[0]
309 + if (!obj) return cb(new Error('empty message'))
310 + if (obj.value) obj = obj.value
311 + gotMsg(obj, cb)
312 + } catch(e) {
313 + return cb(e)
314 + }
315 + })
316 + })
317 + req.removeListener('error', cb)
318 + function gotMsg(value, cb) {
319 + var encoded = new Buffer(JSON.stringify(value, null, 2), 'binary')
320 + var hash = crypto.createHash('sha256').update(encoded).digest('base64')
321 + var id1 = '%' + hash + '.sha256'
322 + if (id !== id1) return cb(new Error('mismatched hash ' + id + ' ' + id1))
323 + cb(null, value)
324 + }
325 +})
326 +
327 +function blobsSize(id, cb) {
328 + var filename = blobFilename(idToBuf(id))
329 + if (!filename) return cb(new Error('bad id'))
330 + fs.stat(filename, function (err, stats) {
331 + if (err) return cb(err)
332 + cb(null, stats.size)
333 + })
334 +}
335 +
336 +function blobsWant(id, cb) {
337 + var hash = idToBuf(id)
338 + var filename = blobFilename(hash)
339 + cb(new Error('not implemented'))
340 + fetchAddBlob(id, hash, filename, {readIt: false}, function (err) {
341 + if (err) return cb(err)
342 + cb(null, true)
343 + })
344 +}
345 +
346 +function blobsGet(id) {
347 + var filename = blobFilename(idToBuf(id))
348 + if (!filename) return cb(new Error('bad id'))
349 + return pullFile(filename)
350 +}
351 +
352 +var ssbNpmConfig
353 +try {
354 + ssbNpmConfig = JSON.stringify(fs.readFileSync(configPath)).npm
355 +} catch(e) {
356 +}
357 +
358 +var serveNpm1 = SsbNpmRegistry.respond({
359 + whoami: whoami,
360 + get: ssbGet,
361 + blobs: {
362 + size: blobsSize,
363 + want: blobsWant,
364 + get: blobsGet,
365 + }
366 +}, {
367 + ws: {
368 + port: port,
369 + host: host,
370 + },
371 + npm: ssbNpmConfig
372 +})
373 +
374 +function serveNpm(req, res, url) {
375 + req.url = url
376 + serveNpm1(req, res)
377 +}
378 +
379 +function serve(req, res) {
380 + var p = URL.parse(req.url)
381 + if (p.pathname.startsWith('/npm/')) return serveNpm(req, res, p.pathname.substr(4))
382 + if (p.pathname.startsWith('/msg/')) return serveMsg(req, res, p.pathname.substr(5))
383 + if (p.pathname.startsWith('/blobs/get/')) return serveBlobsGet(req, res, p.pathname.substr(11))
384 + return serveStatus(res, 404, 'Not Found')
385 +}
386 +
387 +main(process.argv.slice(2))
bootstrap/usage.txtView
@@ -1,0 +1,6 @@
1 +USAGE: ssb-npm-bootstrap [--version] [--help]
2 + [--port <port>] [--host <host>]
3 + [--msgs-url <url>] [--blobs-url <url>]
4 + [--ws-url <url>] [--viewer-url <url>]
5 + [--msg <id>]...
6 + [--] [<cmd> <args...>]

Built with git-ssb-web