git ssb

0+

cel-desktop / ssb-pkg



Tree: ca90b5ca0579ebdcc0635288bce4978e3fb200f5

Files: ca90b5ca0579ebdcc0635288bce4978e3fb200f5 / prelude / install.js

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

Built with git-ssb-web