git ssb

0+

cel-desktop / ssb-pkg



Tree: d1a209da91481b68bc81030e13ce7326503908c8

Files: d1a209da91481b68bc81030e13ce7326503908c8 / prelude / install.js

11762 bytesRaw
1var os = require('os')
2var fs = require('fs')
3var zlib = require('zlib')
4var http = require('http')
5var stream = require('stream')
6var path = require('path')
7var STORE_CONTENT = 1
8
9var settings = JSON.parse(fs.readFileSync(3))
10
11function ReadBlobs(links) {
12 if (!Array.isArray(links)) throw new TypeError('blob links should be array')
13 this.links = links
14 stream.Readable.call(this, links)
15}
16ReadBlobs.prototype = new stream.Readable()
17ReadBlobs.prototype.constructor = ReadBlobs
18ReadBlobs.prototype.i = 0
19ReadBlobs.prototype.blobsBase = process.env.SSB_BLOBS_BASE
20 || 'http://localhost:8989/blobs/get/'
21ReadBlobs.prototype._read = function (size) {
22 if (!this.req) this._makeReq(this)
23 else if (this.res) this.res.resume()
24}
25ReadBlobs.prototype._destroy = function (err, cb) {
26 if (this.req) this.req.destroy(), this.req = null
27 if (this.res) this.res.destroy(), this.res = null
28 cb(err)
29}
30ReadBlobs.prototype._makeReq = function () {
31 if (this.i >= this.links.length) return this.push(null)
32 var link = this.links[this.i++]
33 var id = typeof link === 'string' ? link : link && link.link
34 var url = this.blobsBase + id
35 console.error(id)
36 var onResp = (res) => {
37 this.res = res
38 if (res.statusCode !== 200) {
39 var reqStatus = res.statusCode + ' ' + res.statusMessage
40 return this.destroy(new Error(reqStatus))
41 }
42 res.on('data', (data) => {
43 if (this.push(data) === false) res.pause()
44 })
45 res.on('end', () => {
46 this._makeReq()
47 })
48 }
49 this.req = http.get(url, onResp).on('error', (err) => {
50 // sometimes localhost resolves to 127.0.0.1 but ssb-ws listens on ::1
51 if (err.code === 'ECONNREFUSED' && err.address === '127.0.0.1') {
52 this.blobsBase = this.blobsBase.replace('localhost', '[::1]')
53 url = this.blobsBase + id
54 http.get(url, onResp).on('error', (err) => {
55 this.destroy(err)
56 })
57 } else {
58 this.destroy(err)
59 }
60 })
61}
62
63function Meter() {
64 stream.Transform.call(this)
65}
66Meter.prototype = new stream.Transform()
67Meter.prototype.constructor = Meter
68Meter.prototype.length = 0
69Meter.prototype._transform = function (buf, encoding, cb) {
70 this.length += buf.length
71 cb(null, buf)
72}
73
74function collect(cb) {
75 var bufs = []
76 return new stream.Writable({
77 write: function (chunk, encoding, cb) {
78 bufs.push(Buffer.from(chunk, encoding))
79 cb()
80 }
81 }).on('finish', function () {
82 cb(null, Buffer.concat(bufs))
83 }).on('error', cb)
84}
85
86function substitute(template, vars) {
87 return Object.keys(vars).reduce(function (str, key) {
88 return str.replace(key, vars[key])
89 }, template)
90}
91
92function discoverPlaceholder (binaryBuffer, searchString, padder) {
93 const placeholder = Buffer.from(searchString);
94 const position = binaryBuffer.indexOf(placeholder);
95 if (position === -1) return { notFound: true };
96 return { position, size: placeholder.length, padder };
97}
98
99function injectPlaceholder (fd, placeholder, value, cb) {
100 const { notFound, position, size, padder } = placeholder;
101 if (notFound) assert(false, 'Placeholder for not found');
102 if (typeof value === 'number') value = value.toString();
103 if (typeof value === 'string') value = Buffer.from(value);
104 const padding = Buffer.from(padder.repeat(size - value.length));
105 value = Buffer.concat([ value, padding ]);
106 fs.write(fd, value, 0, value.length, position, cb);
107}
108
109function discoverPlaceholders (binaryBuffer) {
110 return {
111 BAKERY: discoverPlaceholder(binaryBuffer, '\0' + '// BAKERY '.repeat(20), '\0'),
112 PAYLOAD_POSITION: discoverPlaceholder(binaryBuffer, '// PAYLOAD_POSITION //', ' '),
113 PAYLOAD_SIZE: discoverPlaceholder(binaryBuffer, '// PAYLOAD_SIZE //', ' '),
114 PRELUDE_POSITION: discoverPlaceholder(binaryBuffer, '// PRELUDE_POSITION //', ' '),
115 PRELUDE_SIZE: discoverPlaceholder(binaryBuffer, '// PRELUDE_SIZE //', ' ')
116 };
117}
118
119function injectPlaceholders (fd, placeholders, values, cb) {
120 injectPlaceholder(fd, placeholders.BAKERY, values.BAKERY, (error) => {
121 if (error) return cb(error);
122 injectPlaceholder(fd, placeholders.PAYLOAD_POSITION, values.PAYLOAD_POSITION, (error2) => {
123 if (error2) return cb(error2);
124 injectPlaceholder(fd, placeholders.PAYLOAD_SIZE, values.PAYLOAD_SIZE, (error3) => {
125 if (error3) return cb(error3);
126 injectPlaceholder(fd, placeholders.PRELUDE_POSITION, values.PRELUDE_POSITION, (error4) => {
127 if (error4) return cb(error4);
128 injectPlaceholder(fd, placeholders.PRELUDE_SIZE, values.PRELUDE_SIZE, cb);
129 });
130 });
131 });
132 });
133}
134
135function makeBakeryValueFromBakes (bakes) {
136 const parts = [];
137 if (bakes.length) {
138 for (let i = 0; i < bakes.length; i += 1) {
139 parts.push(Buffer.from(bakes[i]));
140 parts.push(Buffer.alloc(1));
141 }
142 parts.push(Buffer.alloc(1));
143 }
144 return Buffer.concat(parts);
145}
146
147function makePreludeBufferFromPrelude (prelude) {
148 return Buffer.from(
149 '(function(process, require, console, EXECPATH_FD, PAYLOAD_POSITION, PAYLOAD_SIZE) { ' +
150 prelude +
151 '\n})' // dont remove \n
152 );
153}
154
155function plusx(file) {
156 const s = fs.statSync(file)
157 const newMode = s.mode | 64 | 8 | 1
158 if (s.mode === newMode) return
159 const base8 = newMode.toString(8).slice(-3)
160 fs.chmodSync(file, base8)
161}
162
163function mkdirp(dir) {
164 try { fs.mkdirSync(path.dirname(path.dirname(dir))) } catch(e) {}
165 try { fs.mkdirSync(path.dirname(dir)) } catch(e) {}
166 fs.mkdirSync(dir)
167 console.error(dir)
168}
169
170function writableDir(dir) {
171 try {
172 fs.accessSync(dir, fs.constants.W_OK)
173 return true
174 } catch(e) {
175 if (e.code === 'ENOENT') {
176 try {
177 mkdirp(dir)
178 return true
179 } catch(e) {}
180 }
181 }
182}
183
184function first(array, fn) {
185 for (var i = 0; i < array.length; i++) {
186 var value = array[i]
187 var result = fn(value)
188 if (result) return value
189 }
190}
191
192function isReadable(file) {
193 try {
194 fs.accessSync(file, fs.constants.R_OK)
195 return true
196 } catch(e) {}
197}
198
199function readProfile() {
200 try {
201 var profile = first([
202 process.env.PROFILE,
203 path.join(homeDir, '.profile')
204 ].filter(Boolean), isReadable)
205 if (profile) return fs.readFileSync(profile, 'utf8')
206 } catch(e) {}
207}
208
209function useConditionalDirFromProfile() {
210 // Use ~/.local/bin or ~/bin if ~/.profile would detect it.
211
212 var profile = readProfile()
213 if (!profile) return
214
215 if (profile.indexOf(
216'if [ -d "$HOME/.local/bin" ] ; then\n PATH="$HOME/.local/bin:$PATH"\nfi'
217 ) > 0) {
218 dir = path.join(homeDir, '.local', 'bin')
219 if (writableDir(dir)) return dir
220 }
221
222 if (profile.indexOf(
223'if [ -d "$HOME/bin" ] ; then\n PATH="$HOME/bin:$PATH"\nfi'
224 ) > 0) {
225 dir = path.join(homeDir, 'bin')
226 if (writableDir(dir)) return dir
227 }
228}
229
230function pickBinDir() {
231 var isRoot = (process.env.UID || process.geteuid()) === 0
232 var userBinDirs = isRoot ? [
233 // If root user, install system-wide.
234 ] : [
235 path.join(homeDir, '.local', 'bin'),
236 path.join(homeDir, 'bin')
237 ]
238 var favoriteDirs = [
239 process.env.PREFIX && path.join(process.env.PREFIX, 'bin')
240 ].concat(userBinDirs).concat([
241 '/usr/local/bin',
242 '/opt/bin'
243 ]).filter(Boolean)
244
245 var dirsInPath = process.env.PATH ? process.env.PATH.split(':') : []
246 function isDirInPath(dir) {
247 return dirsInPath.indexOf(dir) > -1
248 }
249 var favoriteDirsInPath = favoriteDirs.filter(isDirInPath)
250 var dir = first(favoriteDirsInPath, writableDir)
251 if (dir) return dir
252
253 dir = useConditionalDirFromProfile()
254 if (dir) {
255 console.error('Note: restart your shell to use new $PATH')
256 return dir
257 }
258
259 dir = first(favoriteDirs, writableDir)
260 if (dir) {
261 console.error(`Warning: installing to directory not in $PATH.
262Run this and add it to your ~/.profile or similar:
263 export PATH="${dir.replace(homeDir, '$HOME')}:$PATH"`)
264 return dir
265 }
266
267 console.error('Error: no directory to install to. Set $BIN_DIR, or add '
268 + (isRoot ? '/usr/local/bin' : '~/.local/bin')
269 + ' to your $PATH, and make sure you can write to it.')
270 process.exit(1)
271}
272
273var homeDir = process.env.HOME || os.homedir()
274var binDir = process.env.BIN_DIR || pickBinDir()
275var binPath = path.join(binDir, settings.binName)
276var binTmp = binPath + '~'
277
278// assume the node binary is the pkg one to use
279var nodeBuffer = fs.readFileSync(process.execPath)
280
281var placeholders = discoverPlaceholders(nodeBuffer)
282var payloadPosition = nodeBuffer.length, payloadSize
283var preludePosition, preludeSize
284var vfs
285var binFd, writeStream
286
287try {
288 fs.statSync(binDir)
289} catch(e) {
290 mkdirp(binDir)
291}
292
293function writeNodeBinary() {
294 fs.open(binTmp, 'w+', function (err, fd) {
295 if (err) throw err
296 binFd = fd
297 writeStream = fs.createWriteStream(null, {fd: fd})
298 writeStream.write(nodeBuffer, function (err) {
299 if (err) throw err
300 getVfs()
301 })
302 })
303}
304
305function getVfs() {
306 new ReadBlobs(settings.vfsBlobs)
307 .pipe(collect(function (err, vfsData) {
308 if (err) throw err
309 vfs = JSON.parse(vfsData)
310 getPayload()
311 }))
312}
313
314function getPayload() {
315 var payloadMeter = new Meter()
316 var readPayload = new ReadBlobs(settings.payloadBlobs)
317 .pipe(zlib.createGunzip())
318 .pipe(payloadMeter)
319 readPayload.pipe(writeStream, {end: false})
320 readPayload.on('end', function () {
321 payloadSize = payloadMeter.length
322 addFileBlobs()
323 })
324}
325
326function addFileBlobs() {
327 var offset = payloadSize
328 var fileBlobs = settings.fileBlobs
329 var queue = []
330 function enqueue(fileBlobsToAdd) {
331 for (var snap in fileBlobsToAdd) {
332 var ids = fileBlobsToAdd[snap]
333 queue.push({snap: snap, ids: ids})
334 }
335 }
336 if (Array.isArray(fileBlobs)) {
337 enqueue(fileBlobs)
338 } else [
339 os.platform() + '-' + os.arch(),
340 '*'
341 ].forEach(function (group) {
342 enqueue(fileBlobs[group])
343 })
344 ;(function next() {
345 if (!queue.length) return getPrelude()
346 var item = queue.shift()
347 var snap = item.snap, ids = item.ids
348
349 var fileMeter = new Meter()
350 var readFile = new ReadBlobs(ids)
351 .pipe(fileMeter)
352 readFile.pipe(writeStream, {end: false})
353 readFile.on('end', function () {
354 var fileSize = fileMeter.length
355 vfs[snap][STORE_CONTENT] = [offset, fileSize]
356 offset += fileSize
357 payloadSize += fileSize
358 next()
359 })
360 })()
361}
362
363function getPrelude() {
364 preludePosition = payloadPosition + payloadSize
365 new ReadBlobs(settings.preludeBlobs)
366 .pipe(collect(function (err, preludeData) {
367 if (err) throw err
368 preludeData = makePreludeBufferFromPrelude(
369 substitute(preludeData.toString('utf8'), {
370 '%VIRTUAL_FILESYSTEM%': JSON.stringify(vfs),
371 '%DEFAULT_ENTRYPOINT%': JSON.stringify(settings.entrypoint)
372 })
373 )
374 preludeSize = preludeData.length
375 writeStream.write(preludeData, function (err) {
376 if (err) throw err
377 inject()
378 })
379 }))
380}
381
382function inject() {
383 injectPlaceholders(binFd, placeholders, {
384 BAKERY: makeBakeryValueFromBakes(settings.bakes),
385 PAYLOAD_POSITION: payloadPosition,
386 PAYLOAD_SIZE: payloadSize,
387 PRELUDE_POSITION: preludePosition,
388 PRELUDE_SIZE: preludeSize
389 }, function (err) {
390 if (err) throw err
391 fs.close(binFd, function (err) {
392 if (err) throw err
393 putBins()
394 })
395 })
396}
397
398function addOtherBin(name, snap) {
399 var script = `#!/bin/sh
400PKG_ENTRYPOINT=${snap} exec ${settings.binName} "$@"
401`
402 var binPath = path.join(binDir, name)
403 var binTmp = binPath + '~'
404 fs.writeFileSync(binTmp, script)
405 plusx(binTmp)
406 fs.renameSync(binTmp, binPath)
407 console.log(binPath)
408}
409
410function putBins() {
411 plusx(binTmp)
412 fs.renameSync(binTmp, binPath)
413
414 var others = settings.otherEntrypoints
415 if (others) for (var name in others) {
416 var snap = others[name]
417 addOtherBin(name, snap)
418 }
419
420 console.log(binPath)
421 process.exit(0)
422}
423
424writeNodeBinary()
425

Built with git-ssb-web