git ssb

0+

cel-desktop / ssb-pkg



Tree: c5c7a8ffadbd51aa28a5f3f096fe43f69405da44

Files: c5c7a8ffadbd51aa28a5f3f096fe43f69405da44 / prelude / install.js

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

Built with git-ssb-web