@peerbit/server 1.1.2 → 2.0.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/lib/esm/cli.js CHANGED
@@ -1,9 +1,26 @@
1
1
  import { createTestDomain, getDomainFromConfig, loadConfig, startCertbot, } from "./domain.js";
2
2
  import { startServerWithNode } from "./server.js";
3
3
  import { createRecord } from "./aws.js";
4
- import { getHomeConfigDir } from "./config.js";
4
+ import { getHomeConfigDir, getPackageName, getRemotesPath } from "./config.js";
5
5
  import chalk from "chalk";
6
6
  import { client } from "./client.js";
7
+ import { exit } from "process";
8
+ import readline from "readline";
9
+ import fs from "fs";
10
+ import path from "path";
11
+ import { toBase64 } from "@peerbit/crypto";
12
+ import { Remotes } from "./remotes.js";
13
+ const padString = function (string, padding, padChar = " ") {
14
+ const val = string.valueOf();
15
+ if (Math.abs(padding) <= val.length) {
16
+ return val;
17
+ }
18
+ const m = Math.max(Math.abs(padding) - string.length || 0, 0);
19
+ const pad = Array(m + 1).join(String(padChar).charAt(0));
20
+ // var pad = String(c || ' ').charAt(0).repeat(Math.abs(n) - this.length);
21
+ return padding < 0 ? pad + val : val + pad;
22
+ // return (n < 0) ? val + pad : pad + val;
23
+ };
7
24
  export const cli = async (args) => {
8
25
  const yargs = await import("yargs");
9
26
  if (!args) {
@@ -15,28 +32,56 @@ export const cli = async (args) => {
15
32
  .command({
16
33
  command: "start",
17
34
  describe: "Start node",
18
- builder: {
19
- directory: {
20
- describe: "Directory for all data created by the node",
35
+ builder: (yargs) => {
36
+ yargs
37
+ .option("directory", {
38
+ describe: "Peerbit directory",
21
39
  defaultDescription: "~.peerbit",
22
40
  type: "string",
23
- default: await getHomeConfigDir(),
24
- },
25
- bootstrap: {
41
+ alias: "d",
42
+ default: getHomeConfigDir(),
43
+ })
44
+ .option("bootstrap", {
26
45
  describe: "Whether to connect to bootstap nodes on startup",
27
46
  type: "boolean",
28
47
  default: false,
29
- },
48
+ })
49
+ .option("reset", {
50
+ describe: "If true, then programs opened during last session will not be opened",
51
+ type: "boolean",
52
+ default: false,
53
+ alias: "r",
54
+ })
55
+ .option("password", {
56
+ describe: "Setup password so you can interact with the node remotely",
57
+ type: "string",
58
+ defaultDescription: "The password from the last session will be used or a password will be generated",
59
+ default: undefined,
60
+ })
61
+ .option("port-api", {
62
+ describe: "Set API server port. Only modify this when testing locally, since NGINX config depends on the default value",
63
+ type: "number",
64
+ default: undefined,
65
+ })
66
+ .option("port-node", {
67
+ describe: "Set Libp2p listen port. Only modify this when testing locally, since NGINX config depends on the default value",
68
+ type: "number",
69
+ default: undefined,
70
+ });
71
+ return yargs;
30
72
  },
31
73
  handler: async (args) => {
32
74
  await startServerWithNode({
33
75
  directory: args.directory,
34
76
  domain: await loadConfig().then((config) => config ? getDomainFromConfig(config) : undefined),
77
+ ports: { api: args["port-api"], node: args["port-node"] },
35
78
  bootstrap: args.bootstrap,
79
+ password: args.password,
80
+ newSession: args.reset,
36
81
  });
37
82
  },
38
83
  })
39
- .command("domain", "Setup a domain and certificate", (yargs) => {
84
+ .command("domain", "Setup a domain and certificate for this node", (yargs) => {
40
85
  yargs
41
86
  .command({
42
87
  command: "test",
@@ -62,7 +107,6 @@ export const cli = async (args) => {
62
107
  handler: async (args) => {
63
108
  const domain = await createTestDomain();
64
109
  await startCertbot(domain, args.email, args.outdir, args.wait);
65
- const { exit } = await import("process");
66
110
  exit();
67
111
  },
68
112
  })
@@ -131,193 +175,476 @@ export const cli = async (args) => {
131
175
  : undefined,
132
176
  });
133
177
  await startCertbot(args.domain, args.email, args.outdir, args.wait);
134
- const { exit } = await import("process");
135
178
  exit();
136
179
  },
137
180
  })
138
181
  .strict()
139
182
  .demandCommand();
140
183
  })
141
- .command("network", "Manage network", (yargs) => {
142
- yargs
143
- .command({
144
- command: "bootstrap",
145
- describe: "Connect to bootstrap nodes",
146
- handler: async () => {
147
- const c = await client();
148
- await c.network.bootstrap();
149
- },
150
- })
151
- .strict()
152
- .demandCommand();
153
- })
154
- .command("topic", "Manage topics the node is listening to", (yargs) => {
155
- yargs
184
+ .command("remote", "Handle remote nodes", (innerYargs) => {
185
+ innerYargs
156
186
  .command({
157
187
  command: "list",
158
188
  aliases: "ls",
159
- describe: "List all topics",
160
- builder: (yargs) => {
161
- yargs.option("replicate", {
162
- type: "boolean",
163
- describe: "Replicate data on this topic",
164
- alias: "r",
165
- default: false,
166
- });
167
- return yargs;
168
- },
169
- handler: async (args) => {
170
- /* const c = await client();
171
- const topics = await c.topics.get(args.replicate);
172
- if (topics?.length > 0) {
173
- console.log("Topic (" + topics.length + "):");
174
- for (const t of topics) {
175
- console.log(t);
176
- }
177
- } else {
178
- console.log("Not subscribed to any topics");
179
- } */
180
- console.error("Not implemented");
181
- },
182
- })
183
- .strict()
184
- .demandCommand();
185
- return yargs;
186
- })
187
- .command("program", "Manage programs", (yargs) => {
188
- yargs
189
- .command({
190
- command: "status <address>",
191
- describe: "Is a program open",
189
+ describe: "List remotes",
192
190
  builder: (yargs) => {
193
- yargs.positional("address", {
191
+ yargs.option("directory", {
192
+ describe: "Peerbit directory",
193
+ defaultDescription: "~.peerbit",
194
194
  type: "string",
195
- describe: "Program address",
196
- demandOption: true,
195
+ alias: "d",
196
+ default: getHomeConfigDir(),
197
197
  });
198
198
  return yargs;
199
199
  },
200
200
  handler: async (args) => {
201
- const c = await client();
202
- const program = await c.program.has(args.address);
203
- if (!program) {
204
- console.log(chalk.red("Closed"));
201
+ const remotes = new Remotes(getRemotesPath(args.directory));
202
+ const allRemotes = await remotes.all();
203
+ const maxNameLength = allRemotes
204
+ .map((x) => x.name.length)
205
+ .reduce((prev, c, i) => {
206
+ return Math.max(prev, c);
207
+ }, 0);
208
+ const all = await remotes.all();
209
+ if (all.length > 0) {
210
+ for (const remote of all) {
211
+ console.log(padString(remote.name, maxNameLength + 10), remote.address);
212
+ }
205
213
  }
206
214
  else {
207
- console.log(chalk.green("Open"));
215
+ console.log("No remotes found!");
208
216
  }
209
217
  },
210
218
  })
211
219
  .command({
212
- command: "drop <address>",
213
- describe: "Drop a program",
220
+ command: "add <name> <address> <password>",
221
+ describe: "Add remote",
214
222
  builder: (yargs) => {
215
- yargs.positional("address", {
223
+ yargs
224
+ .positional("address", {
216
225
  type: "string",
217
- describe: "Program address",
226
+ describe: "Remote name",
218
227
  demandOption: true,
228
+ })
229
+ .positional("name", {
230
+ type: "string",
231
+ describe: "Remote address",
232
+ demandOption: true,
233
+ })
234
+ .positional("password", {
235
+ type: "string",
236
+ describe: "Password",
237
+ demandOption: true,
238
+ })
239
+ .option("directory", {
240
+ describe: "Peerbit directory",
241
+ defaultDescription: "~.peerbit",
242
+ type: "string",
243
+ alias: "d",
244
+ default: getHomeConfigDir(),
219
245
  });
220
246
  return yargs;
221
247
  },
222
248
  handler: async (args) => {
223
- const c = await client();
224
- await c.program.drop(args.address);
249
+ if (args.name === "localhost") {
250
+ throw new Error("Remote can not be named 'localhost'");
251
+ }
252
+ const api = await client(args.password, args.address);
253
+ try {
254
+ await api.program.list();
255
+ }
256
+ catch (error) {
257
+ throw new Error("Failed to add remote: " + error?.toString());
258
+ }
259
+ if (!fs.existsSync(args.directory)) {
260
+ fs.mkdirSync(args.directory, { recursive: true });
261
+ }
262
+ const remotes = new Remotes(getRemotesPath(args.directory));
263
+ remotes.add(args.name, args.address, args.password);
225
264
  },
226
265
  })
227
266
  .command({
228
- command: "close <address>",
229
- describe: "Close a program",
267
+ command: "remove <name>",
268
+ describe: "Remove a remote",
230
269
  builder: (yargs) => {
231
- yargs.positional("address", {
270
+ yargs
271
+ .positional("name", {
232
272
  type: "string",
233
- describe: "Program address",
273
+ describe: "Remote address",
234
274
  demandOption: true,
275
+ })
276
+ .option("directory", {
277
+ describe: "Peerbit directory",
278
+ defaultDescription: "~.peerbit",
279
+ type: "string",
280
+ alias: "d",
281
+ default: getHomeConfigDir(),
235
282
  });
236
283
  return yargs;
237
284
  },
238
285
  handler: async (args) => {
239
- const c = await client();
240
- await c.program.close(args.address);
241
- },
242
- })
243
- .command({
244
- command: "list",
245
- describe: "List all running programs",
246
- aliases: "ls",
247
- handler: async (args) => {
248
- const c = await client();
249
- const list = await c.program.list();
250
- console.log(`Running programs (${list.length}):`);
251
- list.forEach((p) => {
252
- console.log(chalk.green(p));
253
- });
286
+ const remotes = new Remotes(getRemotesPath(args.directory));
287
+ if (remotes.remove(args.name)) {
288
+ console.log(chalk.green("Removed remote with name: " + args.name));
289
+ }
290
+ else {
291
+ console.log(chalk.red("Did not find any remote with name: " + args.name));
292
+ }
254
293
  },
255
294
  })
256
295
  .command({
257
- command: "open [program]",
258
- describe: "Open program",
296
+ command: "connect [name...]",
297
+ describe: "Connect to remote(s)",
259
298
  builder: (yargs) => {
260
- yargs.positional("program", {
299
+ yargs
300
+ .positional("name", {
261
301
  type: "string",
262
- describe: "Identifier",
263
- demandOption: true,
264
- });
265
- yargs.option("base64", {
266
- type: "string",
267
- describe: "Base64 encoded serialized",
268
- alias: "b",
269
- });
270
- yargs.option("variant", {
302
+ describe: "Remote name",
303
+ default: "localhost",
304
+ demandOption: false,
305
+ array: true,
306
+ })
307
+ .option("directory", {
308
+ describe: "Peerbit directory",
309
+ defaultDescription: "~.peerbit",
271
310
  type: "string",
272
- describe: "Variant name",
273
- alias: "v",
311
+ alias: "d",
312
+ default: getHomeConfigDir(),
274
313
  });
275
314
  return yargs;
276
315
  },
277
- handler: async (args) => {
278
- const c = await client();
279
- if (!args.base64 && !args.variant) {
280
- throw new Error("Either base64 or variant argument needs to be provided");
281
- }
282
- let startArg;
283
- if (args.base64) {
284
- startArg = {
285
- base64: args.base64,
286
- };
316
+ handler: async (connectArgs) => {
317
+ const names = connectArgs.name;
318
+ const apis = [];
319
+ console.log(getRemotesPath(connectArgs.directory));
320
+ if (names.length > 0) {
321
+ const remotes = new Remotes(getRemotesPath(connectArgs.directory));
322
+ for (const name of names) {
323
+ if (name === "localhost") {
324
+ const config = await import("./config.js");
325
+ const adminPassword = await config.loadPassword(config.getServerConfigPath(connectArgs.directory));
326
+ apis.push({
327
+ log: (string) => console.log("localhost: " + string),
328
+ name: "localhost",
329
+ api: await client(adminPassword),
330
+ });
331
+ }
332
+ else {
333
+ const remote = remotes.getByName(name);
334
+ if (!remote) {
335
+ throw new Error("Missing remote with name: " + name);
336
+ }
337
+ let logFn;
338
+ if (names.length > 0) {
339
+ logFn = (string) => console.log(name + ": " + string);
340
+ }
341
+ else {
342
+ logFn = (string) => console.log(string);
343
+ }
344
+ apis.push({
345
+ log: logFn,
346
+ name,
347
+ api: await client(remote.password, remote.address),
348
+ });
349
+ }
350
+ }
287
351
  }
288
- else {
289
- startArg = {
290
- variant: args.variant,
291
- };
352
+ // try if authenticated
353
+ for (const api of apis) {
354
+ try {
355
+ await api.api.program.list();
356
+ }
357
+ catch (error) {
358
+ throw new Error(`Failed to connect to '${api.name}': ${error?.toString()}`);
359
+ }
292
360
  }
293
- const address = await c.program.open(startArg);
294
- console.log("Started program with address: ");
295
- console.log(chalk.green(address.toString()));
361
+ const capi = () => yargs
362
+ .default()
363
+ .command("peer", "Peer info", (yargs) => {
364
+ yargs
365
+ .command({
366
+ command: "id",
367
+ describe: "Get peer id",
368
+ handler: async (args) => {
369
+ for (const api of apis) {
370
+ api.log((await api.api.peer.id.get()).toString());
371
+ }
372
+ },
373
+ })
374
+ .command({
375
+ command: "address",
376
+ describe: "Get addresses",
377
+ handler: async (args) => {
378
+ for (const api of apis) {
379
+ (await api.api.peer.addresses.get()).forEach((x) => api.log(x.toString()));
380
+ }
381
+ },
382
+ })
383
+ .strict()
384
+ .demandCommand();
385
+ return yargs;
386
+ })
387
+ .command("network", "Manage network", (yargs) => {
388
+ yargs
389
+ .command({
390
+ command: "bootstrap",
391
+ describe: "Connect to bootstrap nodes",
392
+ handler: async () => {
393
+ for (const api of apis) {
394
+ await api.api.network.bootstrap();
395
+ }
396
+ },
397
+ })
398
+ .strict()
399
+ .demandCommand();
400
+ })
401
+ .command("topic", "Manage topics the node is listening to", (yargs) => {
402
+ yargs
403
+ .command({
404
+ command: "list",
405
+ aliases: "ls",
406
+ describe: "List all topics",
407
+ builder: (yargs) => {
408
+ yargs.option("replicate", {
409
+ type: "boolean",
410
+ describe: "Replicate data on this topic",
411
+ aliases: "r",
412
+ default: false,
413
+ });
414
+ return yargs;
415
+ },
416
+ handler: async (args) => {
417
+ /* const c = await client();
418
+ const topics = await c.topics.get(args.replicate);
419
+ if (topics?.length > 0) {
420
+ console.log("Topic (" + topics.length + "):");
421
+ for (const t of topics) {
422
+ console.log(t);
423
+ }
424
+ } else {
425
+ console.log("Not subscribed to any topics");
426
+ } */
427
+ console.error("Not implemented");
428
+ },
429
+ })
430
+ .strict()
431
+ .demandCommand();
432
+ return yargs;
433
+ })
434
+ .command("program", "Manage programs", (yargs) => {
435
+ yargs
436
+ .command({
437
+ command: "status <address>",
438
+ describe: "Is a program open",
439
+ builder: (yargs) => {
440
+ yargs.positional("address", {
441
+ type: "string",
442
+ describe: "Program address",
443
+ demandOption: true,
444
+ });
445
+ return yargs;
446
+ },
447
+ handler: async (args) => {
448
+ for (const api of apis) {
449
+ const program = await api.api.program.has(args.address);
450
+ if (!program) {
451
+ api.log(chalk.red("Closed"));
452
+ }
453
+ else {
454
+ api.log(chalk.green("Open"));
455
+ }
456
+ }
457
+ },
458
+ })
459
+ .command({
460
+ command: "drop <address>",
461
+ describe: "Drop a program",
462
+ builder: (yargs) => {
463
+ yargs.positional("address", {
464
+ type: "string",
465
+ describe: "Program address",
466
+ demandOption: true,
467
+ });
468
+ return yargs;
469
+ },
470
+ handler: async (args) => {
471
+ for (const api of apis) {
472
+ try {
473
+ await api.api.program.drop(args.address);
474
+ }
475
+ catch (error) {
476
+ api.log(chalk.red(`Failed to drop ${args.address}: ${error.toString()}`));
477
+ }
478
+ }
479
+ },
480
+ })
481
+ .command({
482
+ command: "close <address>",
483
+ describe: "Close a program",
484
+ builder: (yargs) => {
485
+ yargs.positional("address", {
486
+ type: "string",
487
+ describe: "Program address",
488
+ demandOption: true,
489
+ });
490
+ return yargs;
491
+ },
492
+ handler: async (args) => {
493
+ for (const api of apis) {
494
+ await api.api.program.close(args.address);
495
+ }
496
+ },
497
+ })
498
+ .command({
499
+ command: "list",
500
+ describe: "List all running programs",
501
+ aliases: "ls",
502
+ handler: async (args) => {
503
+ for (const api of apis) {
504
+ const list = await api.api.program.list();
505
+ api.log(`Running programs (${list.length}):`);
506
+ list.forEach((p) => {
507
+ api.log(chalk.green(p));
508
+ });
509
+ }
510
+ },
511
+ })
512
+ .command({
513
+ command: "open [program]",
514
+ describe: "Open program",
515
+ builder: (yargs) => {
516
+ yargs.positional("program", {
517
+ type: "string",
518
+ describe: "Identifier",
519
+ demandOption: true,
520
+ });
521
+ yargs.option("base64", {
522
+ type: "string",
523
+ describe: "Base64 encoded serialized",
524
+ aliases: "b",
525
+ });
526
+ yargs.option("variant", {
527
+ type: "string",
528
+ describe: "Variant name",
529
+ aliases: "v",
530
+ });
531
+ return yargs;
532
+ },
533
+ handler: async (args) => {
534
+ if (!args.base64 && !args.variant) {
535
+ throw new Error("Either base64 or variant argument needs to be provided");
536
+ }
537
+ let startArg;
538
+ if (args.base64) {
539
+ startArg = {
540
+ base64: args.base64,
541
+ };
542
+ }
543
+ else {
544
+ startArg = {
545
+ variant: args.variant,
546
+ };
547
+ }
548
+ for (const api of apis) {
549
+ const address = await api.api.program.open(startArg);
550
+ api.log("Started program with address: ");
551
+ api.log(chalk.green(address.toString()));
552
+ }
553
+ },
554
+ })
555
+ .strict()
556
+ .demandCommand();
557
+ return yargs;
558
+ })
559
+ .command({
560
+ command: "install <package-spec>",
561
+ describe: "install and import a dependency",
562
+ builder: (yargs) => {
563
+ yargs.positional("package-spec", {
564
+ type: "string",
565
+ describe: "Installed dependency will be loaded with js import(...)",
566
+ demandOption: true,
567
+ });
568
+ return yargs;
569
+ },
570
+ handler: async (args) => {
571
+ // if ends with .tgz assume it is a file
572
+ let installCommand;
573
+ const packageName = args["package-spec"];
574
+ if (packageName.endsWith(".tgz")) {
575
+ const packagePath = path.isAbsolute(packageName)
576
+ ? packageName
577
+ : path.join(process.cwd(), packageName);
578
+ const buffer = fs.readFileSync(packagePath);
579
+ const base64 = toBase64(buffer);
580
+ installCommand = {
581
+ type: "tgz",
582
+ name: await getPackageName(packageName),
583
+ base64,
584
+ };
585
+ }
586
+ else {
587
+ installCommand = { type: "npm", name: packageName };
588
+ }
589
+ for (const api of apis) {
590
+ const newPrograms = await api.api.dependency.install(installCommand);
591
+ api.log(`New programs available (${newPrograms.length}):`);
592
+ newPrograms.forEach((p) => {
593
+ api.log(chalk.green(p));
594
+ });
595
+ }
596
+ },
597
+ })
598
+ .command({
599
+ command: "restart",
600
+ describe: "Restart the server",
601
+ handler: async () => {
602
+ for (const api of apis) {
603
+ await api.api.restart();
604
+ }
605
+ },
606
+ })
607
+ .command({
608
+ command: "terminate",
609
+ describe: "Terminate the server",
610
+ handler: async () => {
611
+ for (const api of apis) {
612
+ await api.api.terminate();
613
+ }
614
+ },
615
+ })
616
+ .help()
617
+ .strict()
618
+ .scriptName("")
619
+ .demandCommand()
620
+ .showHelpOnFail(true)
621
+ .exitProcess(false);
622
+ const rl = readline.createInterface({
623
+ input: process.stdin,
624
+ output: process.stdout,
625
+ terminal: true,
626
+ historySize: 100,
627
+ });
628
+ console.log(chalk.green("Connected"));
629
+ console.log("Write 'help' to show commands.\n");
630
+ const first = true;
631
+ rl.prompt(false);
632
+ rl.on("line", async (cargs) => {
633
+ const cmds = capi();
634
+ try {
635
+ await cmds.parse(cargs);
636
+ }
637
+ catch (error) {
638
+ /* console.log(chalk.red("Error parsing command: " + cargs))*/
639
+ }
640
+ rl.prompt(true);
641
+ });
296
642
  },
297
643
  })
644
+ .help()
298
645
  .strict()
299
646
  .demandCommand();
300
- return yargs;
301
- })
302
- .command({
303
- command: "install <package-spec>",
304
- describe: "install and import a dependency",
305
- builder: (yargs) => {
306
- yargs.positional("package-spec", {
307
- type: "string",
308
- describe: "Installed dependency will be loaded with js import(...)",
309
- demandOption: true,
310
- });
311
- return yargs;
312
- },
313
- handler: async (args) => {
314
- const c = await client();
315
- const newPrograms = await c.dependency.install(args["package-spec"]);
316
- console.log(`New programs available (${newPrograms.length}):`);
317
- newPrograms.forEach((p) => {
318
- console.log(chalk.green(p));
319
- });
320
- },
647
+ return innerYargs;
321
648
  })
322
649
  .help()
323
650
  .strict()