Files: e1a1630f31c55420ca08a2a8e36296e3c202f1b1 / prelude / install.js
9106 bytesRaw
1 | var os = require('os') |
2 | var fs = require('fs') |
3 | var zlib = require('zlib') |
4 | var http = require('http') |
5 | var stream = require('stream') |
6 | var path = require('path') |
7 | var STORE_CONTENT = 1 |
8 | |
9 | var settings = JSON.parse(fs.readFileSync(3)) |
10 | |
11 | function 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 | } |
16 | ReadBlobs.prototype = new stream.Readable() |
17 | ReadBlobs.prototype.constructor = ReadBlobs |
18 | ReadBlobs.prototype.i = 0 |
19 | ReadBlobs.prototype.blobsBase = process.env.SSB_BLOBS_BASE |
20 | || 'http://localhost:8989/blobs/get/' |
21 | ReadBlobs.prototype._read = function (size) { |
22 | if (!this.req) this._makeReq(this) |
23 | else if (this.res) this.res.resume() |
24 | } |
25 | ReadBlobs.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 | } |
30 | ReadBlobs.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 | |
62 | function Meter() { |
63 | stream.Transform.call(this) |
64 | } |
65 | Meter.prototype = new stream.Transform() |
66 | Meter.prototype.constructor = Meter |
67 | Meter.prototype.length = 0 |
68 | Meter.prototype._transform = function (buf, encoding, cb) { |
69 | this.length += buf.length |
70 | cb(null, buf) |
71 | } |
72 | |
73 | function 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 | |
85 | function substitute(template, vars) { |
86 | return Object.keys(vars).reduce(function (str, key) { |
87 | return str.replace(key, vars[key]) |
88 | }, template) |
89 | } |
90 | |
91 | function 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 | |
98 | function 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 | |
108 | function 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 | |
118 | function 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 | |
134 | function 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 | |
146 | function 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 | |
154 | function 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 | |
162 | var prefix = process.env.PREFIX || path.join(os.homedir(), '.local') |
163 | var binDir = process.env.BIN_DIR || path.join(prefix, 'bin') |
164 | var binPath = path.join(binDir, settings.binName) |
165 | var binTmp = binPath + '~' |
166 | |
167 | // assume the node binary is the pkg one to use |
168 | var nodeBuffer = fs.readFileSync(process.execPath) |
169 | |
170 | var placeholders = discoverPlaceholders(nodeBuffer) |
171 | var payloadPosition = nodeBuffer.length, payloadSize |
172 | var preludePosition, preludeSize |
173 | var vfs |
174 | var binFd, writeStream |
175 | |
176 | try { |
177 | fs.statSync(binDir) |
178 | } catch(e) { |
179 | try { fs.mkdirSync(path.dirname(path.dirname(binDir))) } catch(e) {} |
180 | try { fs.mkdirSync(path.dirname(binDir)) } catch(e) {} |
181 | fs.mkdirSync(binDir) |
182 | } |
183 | |
184 | function writeNodeBinary() { |
185 | fs.open(binTmp, 'w+', function (err, fd) { |
186 | if (err) throw err |
187 | binFd = fd |
188 | writeStream = fs.createWriteStream(null, {fd: fd}) |
189 | writeStream.write(nodeBuffer, function (err) { |
190 | if (err) throw err |
191 | getVfs() |
192 | }) |
193 | }) |
194 | } |
195 | |
196 | function getVfs() { |
197 | new ReadBlobs(settings.vfsBlobs) |
198 | .pipe(collect(function (err, vfsData) { |
199 | if (err) throw err |
200 | vfs = JSON.parse(vfsData) |
201 | getPayload() |
202 | })) |
203 | } |
204 | |
205 | function getPayload() { |
206 | var payloadMeter = new Meter() |
207 | var readPayload = new ReadBlobs(settings.payloadBlobs) |
208 | .pipe(zlib.createGunzip()) |
209 | .pipe(payloadMeter) |
210 | readPayload.pipe(writeStream, {end: false}) |
211 | readPayload.on('end', function () { |
212 | payloadSize = payloadMeter.length |
213 | addFileBlobs() |
214 | }) |
215 | } |
216 | |
217 | function addFileBlobs() { |
218 | var offset = payloadSize |
219 | var fileBlobs = settings.fileBlobs |
220 | var queue = [] |
221 | function enqueue(fileBlobsToAdd) { |
222 | for (var snap in fileBlobsToAdd) { |
223 | var ids = fileBlobsToAdd[snap] |
224 | queue.push({snap: snap, ids: ids}) |
225 | } |
226 | } |
227 | if (Array.isArray(fileBlobs)) { |
228 | enqueue(fileBlobs) |
229 | } else [ |
230 | os.platform() + '-' + os.arch(), |
231 | '*' |
232 | ].forEach(function (group) { |
233 | enqueue(fileBlobs[group]) |
234 | }) |
235 | ;(function next() { |
236 | if (!queue.length) return getPrelude() |
237 | var item = queue.shift() |
238 | var snap = item.snap, ids = item.ids |
239 | |
240 | var fileMeter = new Meter() |
241 | var readFile = new ReadBlobs(ids) |
242 | .pipe(fileMeter) |
243 | readFile.pipe(writeStream, {end: false}) |
244 | readFile.on('end', function () { |
245 | var fileSize = fileMeter.length |
246 | vfs[snap][STORE_CONTENT] = [offset, fileSize] |
247 | offset += fileSize |
248 | payloadSize += fileSize |
249 | next() |
250 | }) |
251 | })() |
252 | } |
253 | |
254 | function getPrelude() { |
255 | preludePosition = payloadPosition + payloadSize |
256 | new ReadBlobs(settings.preludeBlobs) |
257 | .pipe(collect(function (err, preludeData) { |
258 | if (err) throw err |
259 | preludeData = makePreludeBufferFromPrelude( |
260 | substitute(preludeData.toString('utf8'), { |
261 | '%VIRTUAL_FILESYSTEM%': JSON.stringify(vfs), |
262 | '%DEFAULT_ENTRYPOINT%': JSON.stringify(settings.entrypoint) |
263 | }) |
264 | ) |
265 | preludeSize = preludeData.length |
266 | writeStream.write(preludeData, function (err) { |
267 | if (err) throw err |
268 | inject() |
269 | }) |
270 | })) |
271 | } |
272 | |
273 | function inject() { |
274 | injectPlaceholders(binFd, placeholders, { |
275 | BAKERY: makeBakeryValueFromBakes(settings.bakes), |
276 | PAYLOAD_POSITION: payloadPosition, |
277 | PAYLOAD_SIZE: payloadSize, |
278 | PRELUDE_POSITION: preludePosition, |
279 | PRELUDE_SIZE: preludeSize |
280 | }, function (err) { |
281 | if (err) throw err |
282 | fs.close(binFd, function (err) { |
283 | if (err) throw err |
284 | putBins() |
285 | }) |
286 | }) |
287 | } |
288 | |
289 | function addOtherBin(name, snap) { |
290 | var script = `#!/bin/sh |
291 | PKG_ENTRYPOINT=${snap} exec ${settings.binName} "$@" |
292 | ` |
293 | var binPath = path.join(binDir, name) |
294 | var binTmp = binPath + '~' |
295 | fs.writeFileSync(binTmp, script) |
296 | plusx(binTmp) |
297 | fs.renameSync(binTmp, binPath) |
298 | console.log(binPath) |
299 | } |
300 | |
301 | function putBins() { |
302 | plusx(binTmp) |
303 | fs.renameSync(binTmp, binPath) |
304 | |
305 | var others = settings.otherEntrypoints |
306 | if (others) for (var name in others) { |
307 | var snap = others[name] |
308 | addOtherBin(name, snap) |
309 | } |
310 | |
311 | console.log(binPath) |
312 | process.exit(0) |
313 | } |
314 | |
315 | writeNodeBinary() |
316 |
Built with git-ssb-web