git ssb

0+

cel-desktop / ssb-pkg



Tree: b42712bfb57c4f7d0d82f9add942f8d13b310ed5

Files: b42712bfb57c4f7d0d82f9add942f8d13b310ed5 / prelude / install.js

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

Built with git-ssb-web