@ethernity-cloud/sdk-js 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/pynithy/ipfs.mjs CHANGED
@@ -1,244 +1,465 @@
1
- // ipfsClient.js
2
- import { create, globSource } from 'kubo-rpc-client';
1
+ // ipfs.mjs
2
+ //
3
+ // IPFS client for the Ethernity Cloud JS SDK.
4
+ //
5
+ // The UPLOAD path is a faithful port of the Python SDK's IPFSClient
6
+ // (ethernity_cloud_sdk_py/commands/pynithy/ipfs_client.py): it posts directly
7
+ // to the IPFS HTTP API `/api/v0/add` with a raw multipart body,
8
+ // `wrap-with-directory=true`, `Expect: 100-continue`, an explicit
9
+ // `Content-Length`, and parses the newline-delimited JSON response to find the
10
+ // wrapping-directory root hash (the entry whose `Name` is empty). A 10-attempt
11
+ // exponential-backoff retry wraps both file and directory uploads. This matches
12
+ // the Python pipeline step-for-step so both SDKs upload identically (e.g. for
13
+ // the certex / public-key extraction flow).
14
+ //
15
+ // The DOWNLOAD path keeps the existing kubo-rpc-client based helpers.
16
+ import http from 'http';
17
+ import https from 'https';
18
+ import { URL } from 'url';
3
19
  import { promisify } from 'util';
4
20
  import fs from 'fs';
5
21
  import { Command } from 'commander';
6
22
  import { promises as fss } from 'fs';
7
23
  import path from 'path';
8
- import { MultiBar, Presets } from 'cli-progress';
24
+ import { create } from 'kubo-rpc-client';
9
25
 
10
26
  const program = new Command();
11
27
  const writeFile = promisify(fs.writeFile);
12
28
  const delay = (ms) => new Promise((res) => setTimeout(res, ms));
13
29
  const getRetryDelay = (retryCount, baseDelay = 1) => baseDelay * 2 ** retryCount;
14
30
 
31
+ const RETRY_COUNT = 10; // mirrors Python ipfs_client.RETRY_COUNT
32
+
33
+ // Endpoint + auth, mirroring the Python IPFSClient constructor.
34
+ // apiUrl : e.g. "https://ipfs.ethernity.cloud"
35
+ // addUrl : `${apiUrl}/api/v0/add`
36
+ let apiUrl = null;
37
+ let authHeader = null; // Authorization header value, or null
38
+
39
+ // kubo-rpc-client instance, used only for the download helpers below.
15
40
  let ipfs = null;
16
41
 
17
42
  const initialize = (host, protocol, port, token) => {
18
- if (host.search('http') !== -1) {
19
- ipfs = create(host); //{ timeout: '4m' }
20
- } else if (token === '') {
21
- ipfs = create({
22
- host,
23
- protocol,
24
- port,
25
- // timeout: '4m'
26
- });
43
+ // Normalise the endpoint to a base URL string, matching how the Python client
44
+ // treats `ipfs_endpoint` (it just prepends it to `/api/v0/add`).
45
+ if (host && host.search('http') !== -1) {
46
+ apiUrl = host.replace(/\/+$/, '');
47
+ ipfs = create(host);
27
48
  } else {
28
- ipfs = create({
29
- host,
30
- protocol,
31
- port,
32
- headers: { authorization: token },
33
- // timeout: '4m'
34
- });
49
+ const proto = protocol || 'http';
50
+ const p = port ? `:${port}` : '';
51
+ apiUrl = `${proto}://${host}${p}`.replace(/\/+$/, '');
52
+ const opts = { host, protocol: proto, port };
53
+ if (token) opts.headers = { authorization: token };
54
+ ipfs = create(opts);
35
55
  }
56
+ authHeader = token && token !== '' ? token : null;
36
57
  };
37
58
 
38
- const uploadFileToIPFS = async (filePath) => {
39
- try {
40
- const fileContent = await fss.readFile(filePath);
41
- const response = await ipfs.add({ path: filePath, content: fileContent });
42
- await fss.writeFile(`./IPFS_DOCKER_COMPOSE_HASH.ipfs`, response.cid.toString());
43
- return response.cid.toString();
44
- } catch (e) {
45
- console.error();
46
- return "Failed to upload file to IPFS, please try again.";
47
- }
59
+ // ---------------------------------------------------------------------------
60
+ // Raw multipart upload (Python-parity)
61
+ // ---------------------------------------------------------------------------
62
+
63
+ const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
64
+ const CHECK = '\x1b[92m✔\x1b[0m ';
65
+ const FAIL = '\x1b[91m✘\x1b[0m ';
66
+
67
+ // Recursively collect every file under a directory (absolute paths), like
68
+ // Python's os.walk in upload_dir.
69
+ const gatherFiles = (dirPath) => {
70
+ const out = [];
71
+ const walk = (dir) => {
72
+ for (const name of fs.readdirSync(dir)) {
73
+ const full = path.join(dir, name);
74
+ if (fs.statSync(full).isDirectory()) walk(full);
75
+ else out.push(full);
76
+ }
77
+ };
78
+ walk(dirPath);
79
+ return out;
48
80
  };
49
81
 
82
+ // Build a multipart/form-data body identical in shape to requests_toolbelt's
83
+ // MultipartEncoder: one part per file, the field name AND filename both set to
84
+ // the POSIX relative path, Content-Type application/octet-stream. Returns the
85
+ // full body Buffer + the boundary + total byte length.
86
+ const buildMultipartBody = (files, dirPath) => {
87
+ const boundary =
88
+ '----EthernityCloudFormBoundary' +
89
+ Buffer.from(`${files.length}-${dirPath}`).toString('hex').slice(0, 24);
90
+ const parts = [];
91
+ for (const filepath of files) {
92
+ const rel = path.relative(dirPath, filepath).replace(/\\/g, '/');
93
+ const header =
94
+ `--${boundary}\r\n` +
95
+ `Content-Disposition: form-data; name="${rel}"; filename="${rel}"\r\n` +
96
+ `Content-Type: application/octet-stream\r\n\r\n`;
97
+ parts.push(Buffer.from(header, 'utf8'));
98
+ parts.push(fs.readFileSync(filepath));
99
+ parts.push(Buffer.from('\r\n', 'utf8'));
100
+ }
101
+ parts.push(Buffer.from(`--${boundary}--\r\n`, 'utf8'));
102
+ const body = Buffer.concat(parts);
103
+ return { body, boundary, length: body.length };
104
+ };
50
105
 
51
- const uploadFolderToIPFS = async (folderPath) => {
52
- try {
53
- const addedFiles = [];
54
- const ipfsOptions = {
55
- wrapWithDirectory: true,
56
- pin: true,
57
- hidden: true,
58
- timeout: '5m'
106
+ // Low-level POST to the IPFS add endpoint using Node's http/https so we can set
107
+ // Content-Length + Expect: 100-continue exactly like the Python client and read
108
+ // the streamed NDJSON response. Resolves { statusCode, text }.
109
+ const postAdd = (query, body, boundary, contentLength, onProgress) =>
110
+ new Promise((resolve, reject) => {
111
+ const url = new URL(`${apiUrl}/api/v0/add${query}`);
112
+ const mod = url.protocol === 'https:' ? https : http;
113
+ const headers = {
114
+ 'Content-Type': `multipart/form-data; boundary=${boundary}`,
115
+ 'Content-Length': String(contentLength),
116
+ Expect: '100-continue'
117
+ };
118
+ if (authHeader) headers.Authorization = authHeader;
119
+
120
+ const req = mod.request(
121
+ {
122
+ method: 'POST',
123
+ hostname: url.hostname,
124
+ port: url.port || (url.protocol === 'https:' ? 443 : 80),
125
+ path: `${url.pathname}${url.search}`,
126
+ headers
127
+ },
128
+ (res) => {
129
+ let text = '';
130
+ res.setEncoding('utf8');
131
+ res.on('data', (chunk) => {
132
+ text += chunk;
133
+ });
134
+ res.on('end', () => resolve({ statusCode: res.statusCode, text }));
135
+ }
136
+ );
137
+
138
+ req.on('error', reject);
139
+
140
+ // Honour Expect: 100-continue -- only stream the body once the server
141
+ // approves (mirrors requests' behaviour with Expect: 100-continue).
142
+ let sent = 0;
143
+ const sendBody = () => {
144
+ // Write in MB-ish chunks so we can report progress like Python.
145
+ const CHUNK = 1024 * 1024;
146
+ let offset = 0;
147
+ const writeNext = () => {
148
+ if (offset >= body.length) {
149
+ req.end();
150
+ return;
151
+ }
152
+ const end = Math.min(offset + CHUNK, body.length);
153
+ const ok = req.write(body.subarray(offset, end));
154
+ sent += end - offset;
155
+ offset = end;
156
+ if (onProgress) onProgress(sent);
157
+ if (ok) setImmediate(writeNext);
158
+ else req.once('drain', writeNext);
159
+ };
160
+ writeNext();
59
161
  };
60
162
 
61
- console.log(`Uploading folder to IPFS: ${folderPath}`);
163
+ let bodySent = false;
164
+ req.on('continue', () => {
165
+ if (bodySent) return;
166
+ bodySent = true;
167
+ sendBody();
168
+ });
169
+ // Fallback: if the server never sends 100-continue within a short grace
170
+ // period, send the body anyway (some gateways skip it).
171
+ setTimeout(() => {
172
+ if (bodySent) return;
173
+ bodySent = true;
174
+ sendBody();
175
+ }, 3000);
176
+ });
177
+
178
+ // Parse the NDJSON add response and return the wrapping-directory root hash --
179
+ // the entry whose "Name" is "" (matches Python upload_dir).
180
+ const parseRootHash = (text) => {
181
+ const rootLineHash = () => {
182
+ let root = null;
183
+ let last = null;
184
+ for (const line of text.split('\n')) {
185
+ const t = line.trim();
186
+ if (!t) continue;
187
+ let obj;
188
+ try {
189
+ obj = JSON.parse(t);
190
+ } catch {
191
+ continue;
192
+ }
193
+ last = obj;
194
+ if (obj.Name === '') root = obj.Hash;
195
+ }
196
+ // Some gateways don't emit an empty-Name entry; fall back to the last hash.
197
+ return root || (last && last.Hash) || null;
198
+ };
199
+ return rootLineHash();
200
+ };
201
+
202
+ // Generic retry wrapper mirroring Python's retry_on_failure (10 attempts,
203
+ // 10s initial delay, x2 backoff, small jitter).
204
+ const retryOnFailure = async (fn, label) => {
205
+ let delayMs = 10000;
206
+ const backoff = 2;
207
+ for (let attempt = 0; attempt < RETRY_COUNT; attempt++) {
208
+ try {
209
+ const result = await fn();
210
+ if (result !== null && result !== false && result !== 'Upload failed.') return result;
211
+ throw new Error('empty result');
212
+ } catch (e) {
213
+ process.stdout.write(
214
+ `\n[Retry ${attempt + 1}/${RETRY_COUNT}] ${label} failed: ${e && e.message ? e.message : e}\n`
215
+ );
216
+ if (attempt < RETRY_COUNT - 1) {
217
+ await delay(delayMs + Math.random() * 100);
218
+ delayMs *= backoff;
219
+ }
220
+ }
221
+ }
222
+ console.log('Max retry attempts reached. Operation failed.');
223
+ return null;
224
+ };
62
225
 
63
- const multiBar = new MultiBar({
64
- clearOnComplete: false,
65
- hideCursor: true,
66
- format: 'Progress [{bar}] {percentage}% | {value}/{total} MB'
67
- }, Presets.shades_classic);
226
+ // Upload a single file: POST it to /api/v0/add and return its hash.
227
+ const uploadFileToIPFSOnce = async (filePath) => {
228
+ const files = [filePath];
229
+ const dir = path.dirname(filePath);
230
+ const { body, boundary, length } = buildMultipartBody(files, dir);
231
+ const { statusCode, text } = await postAdd('', body, boundary, length, null);
232
+ if (statusCode !== 200) {
233
+ console.log(`Failed to upload to IPFS. Status code: ${statusCode}`);
234
+ return null;
235
+ }
236
+ // For a single file the last NDJSON line carries its hash.
237
+ let last = null;
238
+ for (const line of text.split('\n')) {
239
+ const t = line.trim();
240
+ if (!t) continue;
241
+ try {
242
+ last = JSON.parse(t);
243
+ } catch {
244
+ /* ignore */
245
+ }
246
+ }
247
+ return last ? last.Hash : null;
248
+ };
68
249
 
69
- const files = [];
70
- const readDirectory = (dir) => {
71
- fs.readdirSync(dir).forEach((file) => {
72
- const fullPath = path.join(dir, file);
73
- if (fs.statSync(fullPath).isDirectory()) {
74
- readDirectory(fullPath);
75
- } else {
76
- files.push({
77
- path: path.relative(folderPath, fullPath),
78
- size: fs.statSync(fullPath).size
79
- });
80
- }
81
- });
82
- };
250
+ const uploadFileToIPFS = async (filePath) => {
251
+ const hash = await retryOnFailure(() => uploadFileToIPFSOnce(filePath), 'upload_file');
252
+ if (hash) {
253
+ try {
254
+ await fss.writeFile(`./IPFS_DOCKER_COMPOSE_HASH.ipfs`, hash);
255
+ } catch {
256
+ /* best effort */
257
+ }
258
+ }
259
+ return hash;
260
+ };
83
261
 
84
- readDirectory(folderPath);
85
- const totalSize = files.reduce((acc, file) => acc + file.size, 0);
86
-
87
- const progressBar = multiBar.create(totalSize / 1024 / 1024, 0);
88
-
89
- for await (const file of ipfs.addAll(files.map(file => ({
90
- path: file.path.replace(/\\/g, '/'),
91
- content: fs.createReadStream(path.join(folderPath, file.path))
92
- })), { ...ipfsOptions })) {
93
- addedFiles.push({
94
- cid: file.cid.toString(),
95
- path: file.path,
96
- size: file.size,
97
- });
98
- progressBar.increment(file.size / 1024 / 1024);
262
+ // Upload a whole directory with wrap-with-directory=true and return the root
263
+ // directory hash -- the exact behaviour of Python's upload_dir.
264
+ const uploadDirOnce = async (dirPath) => {
265
+ const abs = path.resolve(dirPath);
266
+ const files = gatherFiles(abs);
267
+ const totalSize = files.reduce((acc, f) => acc + fs.statSync(f).size, 0);
268
+ const totalMb = Math.floor(totalSize / (1024 * 1024));
269
+ const { body, boundary, length } = buildMultipartBody(files, abs);
270
+
271
+ let lastShownMb = -1;
272
+ let frame = 0;
273
+ const onProgress = (sent) => {
274
+ const mb = Math.floor(sent / (1024 * 1024));
275
+ if (mb > lastShownMb) {
276
+ frame = (frame + 1) % SPINNER_FRAMES.length;
277
+ lastShownMb = mb;
278
+ process.stdout.write(
279
+ `\r\t${SPINNER_FRAMES[frame]} Uploading and pinning enclave to IPFS... ${mb}MB/${totalMb}MB`
280
+ );
99
281
  }
100
- progressBar.update(totalSize / 1024 / 1024);
101
- multiBar.stop();
102
- const _hash = addedFiles.find(file => file.path === '').cid;
103
- // Return the CID of the root directory
104
- await fss.writeFile(`./IPFS_HASH.ipfs`, _hash);
105
- return _hash;
106
- } catch (e) {
107
- return "Upload failed.";
282
+ };
283
+
284
+ const query =
285
+ '?quieter=true&stream-channels=true&wrap-with-directory=true&progress=false&timeout=5m';
286
+ const { statusCode, text } = await postAdd(query, body, boundary, length, onProgress);
287
+
288
+ if (statusCode !== 200) {
289
+ process.stdout.write(`\r\t${FAIL}Uploading and pinning enclave to IPFS\n`);
290
+ console.log(`Failed to upload to IPFS. Status code: ${statusCode}`);
291
+ return false;
292
+ }
293
+ const rootHash = parseRootHash(text);
294
+ if (!rootHash) {
295
+ process.stdout.write(`\r\t${FAIL}Uploading and pinning enclave to IPFS\n`);
296
+ console.log('Failed to upload to IPFS. Could not determine root hash.');
297
+ return false;
298
+ }
299
+ process.stdout.write(`\r\t${CHECK}Uploading and pinning enclave to IPFS\n`);
300
+ return rootHash;
301
+ };
302
+
303
+ const uploadFolderToIPFS = async (folderPath) => {
304
+ const hash = await retryOnFailure(() => uploadDirOnce(folderPath), 'upload_dir');
305
+ if (hash) {
306
+ try {
307
+ await fss.writeFile(`./IPFS_HASH.ipfs`, hash);
308
+ } catch {
309
+ /* best effort */
310
+ }
311
+ return hash;
108
312
  }
313
+ return 'Upload failed.';
109
314
  };
110
315
 
111
- const getFromIPFS = async (hhash, filePath, maxRetries = process.env.REACT_APP_IPFS_RETRIES || 5) => {
316
+ // Dispatcher mirroring Python's IPFSClient.upload(path).
317
+ const upload = async (p) => {
318
+ const stat = fs.statSync(p);
319
+ if (stat.isFile()) return uploadFileToIPFS(p);
320
+ if (stat.isDirectory()) return uploadFolderToIPFS(p);
321
+ console.log(`Path ${p} is neither a file nor a directory.`);
322
+ return null;
323
+ };
324
+
325
+ // ---------------------------------------------------------------------------
326
+ // Download helpers (kubo-rpc-client based; unchanged behaviour)
327
+ // ---------------------------------------------------------------------------
328
+
329
+ const getFromIPFS = async (hhash, filePath, maxRetries = process.env.REACT_APP_IPFS_RETRIES || 5) => {
112
330
  let res = '';
113
331
  let retryCount = 0;
114
- // console.log('Downloading file from IPFS...');
115
- // console.log(`Hash: ${hhash}`);
116
- // console.log(`Max Retries: ${maxRetries}`);
117
-
118
332
  while (retryCount < maxRetries) {
119
333
  try {
120
334
  for await (const file of ipfs.cat(hhash)) {
121
- res += new TextDecoder().decode(file.buffer);
335
+ res += new TextDecoder().decode(file.buffer || file);
122
336
  }
123
337
  await writeFile(filePath, res);
124
338
  return;
125
339
  } catch (error) {
126
340
  console.error(`Error: ${error.message}`);
127
341
  retryCount += 1;
128
-
129
342
  if (retryCount < maxRetries) {
130
- console.log(`Retrying... (${retryCount}/${maxRetries})`);
131
343
  await delay(getRetryDelay(retryCount));
132
344
  continue;
133
345
  } else {
134
- throw new Error("ECError.IPFS_DOWNLOAD_ERROR");
346
+ throw new Error('ECError.IPFS_DOWNLOAD_ERROR');
135
347
  }
136
348
  }
137
349
  }
138
350
  };
139
351
 
140
-
141
352
  const downloadFolderFromIPFS = async (cid, outputPath) => {
142
353
  try {
143
354
  console.log(`Downloading folder from IPFS: ${cid}`);
144
-
145
- const multiBar = new MultiBar({
146
- clearOnComplete: false,
147
- hideCursor: true,
148
- format: 'Progress [{bar}] {percentage}% | {value}/{total} MB'
149
- }, Presets.shades_classic);
150
-
151
355
  const files = [];
152
356
  for await (const file of ipfs.get(cid)) {
153
- console.log(`file: ${JSON.stringify(file, null, 2)}`);
154
357
  if (!file.content) continue;
155
-
156
358
  const filePath = path.join(outputPath, file.path);
157
359
  const dir = path.dirname(filePath);
158
-
159
- if (!fs.existsSync(dir)) {
160
- fs.mkdirSync(dir, { recursive: true });
161
- }
162
-
360
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
163
361
  const writeStream = fs.createWriteStream(filePath);
164
- const progressBar = multiBar.create(file.size / 1024 / 1024, 0);
165
-
166
362
  for await (const chunk of file.content) {
167
363
  writeStream.write(chunk);
168
- progressBar.increment(chunk.length / 1024 / 1024);
169
364
  }
170
-
171
365
  writeStream.end();
172
- progressBar.update(file.size / 1024 / 1024);
173
366
  files.push(filePath);
174
367
  }
175
-
176
- multiBar.stop();
177
368
  console.log(`Downloaded ${files.length} files to ${outputPath}`);
178
369
  } catch (e) {
179
370
  console.error(e);
180
- return "err";
371
+ return 'err';
372
+ }
373
+ };
374
+
375
+ const getContentFromIPFS = async (hash, maxRetries = process.env.REACT_APP_IPFS_RETRIES || 5) => {
376
+ let res = '';
377
+ let retryCount = 0;
378
+ while (retryCount < maxRetries) {
379
+ try {
380
+ for await (const file of ipfs.cat(hash)) {
381
+ res += new TextDecoder().decode(file.buffer || file);
382
+ }
383
+ return res;
384
+ } catch (error) {
385
+ console.error(error.message);
386
+ retryCount += 1;
387
+ if (retryCount < maxRetries) {
388
+ await delay(getRetryDelay(retryCount));
389
+ continue;
390
+ } else {
391
+ throw new Error('ECIPFSDownloadError');
392
+ }
393
+ }
181
394
  }
182
395
  };
183
396
 
397
+ export {
398
+ initialize,
399
+ upload,
400
+ uploadFileToIPFS,
401
+ uploadFolderToIPFS,
402
+ getFromIPFS,
403
+ downloadFolderFromIPFS,
404
+ getContentFromIPFS
405
+ };
406
+
407
+ // ---------------------------------------------------------------------------
408
+ // CLI (unchanged interface: --host --action upload/download --filePath/--folderPath)
409
+ // ---------------------------------------------------------------------------
410
+
184
411
  program
185
412
  .option('--host <host>', 'IPFS host')
186
413
  .option('--hhash <hhash>', 'IPFS hash')
187
414
  .option('--filePath <path>', 'Path to the file')
188
415
  .option('--folderPath <path>', 'Path to the folder')
189
416
  .option('--action <action>', 'Action to perform (upload, download)')
417
+ .option('--token <token>', 'IPFS authorization token')
190
418
  .option('--output <path>', 'Output path for download');
191
419
 
192
- program.parse(process.argv);
420
+ const isMain = process.argv[1] && process.argv[1].endsWith('ipfs.mjs');
193
421
 
194
- const options = program.opts();
422
+ if (isMain) {
423
+ program.parse(process.argv);
424
+ const options = program.opts();
195
425
 
196
- const main = async () => {
197
- const host = options.host || 'localhost';
198
- initialize(host);
426
+ const main = async () => {
427
+ const host = options.host || 'localhost';
428
+ initialize(host, undefined, undefined, options.token || '');
199
429
 
200
- if (options.action === 'upload') {
201
- if (options.filePath) {
202
- const hhash = await uploadFileToIPFS(options.filePath);
203
- console.log(`${hhash}`);
204
- } else if (options.folderPath) {
205
- let retryCount = 0;
206
- let hhash = null;
207
- try {
208
- hhash = await uploadFolderToIPFS(options.folderPath);
430
+ if (options.action === 'upload') {
431
+ if (options.filePath) {
432
+ const hhash = await uploadFileToIPFS(options.filePath);
209
433
  console.log(`${hhash}`);
210
- } catch (e) {
211
- // console.log(e);
212
- }
213
- while ((!hhash || hhash === 'Upload failed.') && retryCount < 3) {
214
- console.log(`Retrying... (${retryCount}/3)`);
215
- hhash = await uploadFolderToIPFS(options.folderPath);
434
+ process.exit(hhash ? 0 : 1);
435
+ } else if (options.folderPath) {
436
+ // retryOnFailure already retries 10x internally; a single call suffices.
437
+ const hhash = await uploadFolderToIPFS(options.folderPath);
216
438
  console.log(`${hhash}`);
217
- retryCount += 1;
439
+ if (!hhash || hhash === 'Upload failed.') {
440
+ console.log(`Failed to upload folder to IPFS, please try again.`);
441
+ process.exit(1);
442
+ }
443
+ process.exit(0);
444
+ } else {
445
+ console.error('Please provide a filePath or folderPath for upload.');
446
+ process.exit(1);
218
447
  }
219
-
220
- if (!hhash || hhash === 'Upload failed.') {
221
- console.log(`Failed to upload folder to IPFS, please try again.`);
448
+ } else if (options.action === 'download') {
449
+ if (options.filePath) {
450
+ await getFromIPFS(options.hhash, options.filePath);
451
+ console.log(`File downloaded. ${options.hhash}`);
452
+ } else if (options.folderPath) {
453
+ await downloadFolderFromIPFS(options.hhash, options.folderPath);
454
+ console.log(`Folder downloaded. ${options.hhash}`);
455
+ } else {
456
+ console.error('Please provide a filePath or folderPath for download.');
222
457
  }
223
- process.exit(0);
224
- } else {
225
- console.error('Please provide a filePath or folderPath for upload.');
226
- }
227
- } else if (options.action === 'download') {
228
- if (options.filePath) {
229
- console.log(`Downloading file from IPFS: ${options.hhash}`);
230
- const content = await getFromIPFS(options.hhash, options.filePath);
231
- console.log(`File downloaded. ${options.hhash}`);
232
- } else if (options.folderPath) {
233
- console.log(`Downloading folder from IPFS: ${options.hhash}`);
234
- const content = await downloadFolderFromIPFS(options.hhash, options.folderPath);
235
- console.log(`Folder downloaded. ${options.hhash}`);
236
458
  } else {
237
- console.error('Please provide a filePath or folderPath for download.');
459
+ console.error('Please provide a valid action (upload, download).');
460
+ process.exit(1);
238
461
  }
239
- } else {
240
- console.error('Please provide a valid action (upload, download).');
241
- }
242
- };
462
+ };
243
463
 
244
- main();
464
+ main();
465
+ }
@@ -6,6 +6,8 @@ services:
6
6
  image: localhost:5000/etny-las
7
7
  entrypoint: "/las_entrypoint.sh"
8
8
  command: "bash -c '/las_entrypoint.sh && /usr/local/bin/las | tee /var/log/las.log'"
9
+ devices:
10
+ - "/dev/sgx_enclave:/dev/sgx_enclave"
9
11
  restart: unless-stopped
10
12
  networks:
11
13
  - ethernity
@@ -26,10 +28,15 @@ services:
26
28
  - SCONE_LAS_ADDR=las
27
29
  - SCONE_CONFIG_ID=__ENCLAVE_NAME_SECURELOCK__/application
28
30
  - SCONE_HEAP=1024M
31
+ # Must match the --stack the securelock binary was signed with (build.mjs),
32
+ # otherwise SCONE recomputes MRENCLAVE at load and re-signs as debug.
33
+ - SCONE_STACK=4M
29
34
  - SCONE_LOG=DEBUG
30
35
  - SCONE_ALLOW_DLOPEN=1
31
36
  - SCONE_EXTENSIONS_PATH=/lib/libbinary-fs.so
32
37
  restart: on-failure
38
+ devices:
39
+ - "/dev/sgx_enclave:/dev/sgx_enclave"
33
40
  networks:
34
41
  - ethernity
35
42
  depends_on:
@@ -50,6 +57,8 @@ services:
50
57
  - SCONE_ALLOW_DLOPEN=1
51
58
  - SCONE_EXTENSIONS_PATH=/lib/libbinary-fs.so
52
59
  restart: on-failure
60
+ devices:
61
+ - "/dev/sgx_enclave:/dev/sgx_enclave"
53
62
  networks:
54
63
  - ethernity
55
64
  depends_on: