git ssb

0+

cel-desktop / ssb-pkg



Tree: 6859afcdedef7118f29d6753ba4933fb14a08f21

Files: 6859afcdedef7118f29d6753ba4933fb14a08f21 / prelude / install.js

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

Built with git-ssb-web