pinokiod 3.9.0 → 3.9.2

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.
Files changed (118) hide show
  1. package/kernel/api/filepicker/index.js +59 -0
  2. package/kernel/api/fs/index.js +6 -4
  3. package/kernel/api/index.js +123 -37
  4. package/kernel/api/input/index.js +2 -0
  5. package/kernel/api/web/index.js +6 -0
  6. package/kernel/bin/caddy.js +127 -0
  7. package/kernel/bin/cloudflared.js +1 -0
  8. package/kernel/bin/conda.js +10 -2
  9. package/kernel/bin/cuda.js +0 -16
  10. package/kernel/bin/ffmpeg.js +4 -6
  11. package/kernel/bin/git.js +55 -29
  12. package/kernel/bin/gxx.js +8 -6
  13. package/kernel/bin/huggingface.js +4 -5
  14. package/kernel/bin/index.js +347 -88
  15. package/kernel/bin/node.js +13 -6
  16. package/kernel/bin/py.js +1 -1
  17. package/kernel/bin/setup.js +146 -0
  18. package/kernel/bin/uv.js +4 -5
  19. package/kernel/bin/zip.js +7 -8
  20. package/kernel/connect/index.js +25 -0
  21. package/kernel/connect/providers/github/README.md +11 -0
  22. package/kernel/connect/providers/x/README.md +96 -0
  23. package/kernel/connect/providers/x/index.js +152 -0
  24. package/kernel/environment.js +50 -1
  25. package/kernel/index.js +152 -32
  26. package/kernel/info.js +4 -2
  27. package/kernel/kv.js +38 -0
  28. package/kernel/loader.js +13 -3
  29. package/kernel/peer.js +161 -0
  30. package/kernel/procs.js +116 -0
  31. package/kernel/prototype.js +62 -0
  32. package/kernel/router/common.js +42 -0
  33. package/kernel/router/connector.js +81 -0
  34. package/kernel/router/index.js +235 -0
  35. package/kernel/router/localhost_home_router.js +84 -0
  36. package/kernel/router/localhost_port_router.js +15 -0
  37. package/kernel/router/localhost_variable_router.js +37 -0
  38. package/kernel/router/peer_home_router.js +15 -0
  39. package/kernel/router/peer_peer_router.js +26 -0
  40. package/kernel/router/peer_port_router.js +45 -0
  41. package/kernel/router/peer_variable_router.js +46 -0
  42. package/kernel/router/processor.js +31 -0
  43. package/kernel/shell.js +141 -60
  44. package/kernel/shells.js +32 -17
  45. package/kernel/store.js +58 -0
  46. package/kernel/sysinfo.js +0 -1
  47. package/kernel/util.js +143 -36
  48. package/package.json +4 -1
  49. package/pipe/index.js +0 -2
  50. package/script/index.js +12 -0
  51. package/server/index.js +1473 -553
  52. package/server/public/Socket.js +5 -1
  53. package/server/public/SourceCodePro-Regular.ttf +0 -0
  54. package/server/public/SpaceMono-Bold.ttf +0 -0
  55. package/server/public/SpaceMono-BoldItalic.ttf +0 -0
  56. package/server/public/SpaceMono-Italic.ttf +0 -0
  57. package/server/public/SpaceMono-Regular.ttf +0 -0
  58. package/server/public/commander.js +40 -0
  59. package/server/public/common.js +60 -0
  60. package/server/public/dropzone-min.js +2 -0
  61. package/server/public/dropzone.css +1 -0
  62. package/server/public/filepond-plugin-file-validate-type.min.js +9 -0
  63. package/server/public/filepond-plugin-image-crop.min.js +9 -0
  64. package/server/public/filepond-plugin-image-edit.min.css +8 -0
  65. package/server/public/filepond-plugin-image-edit.min.js +9 -0
  66. package/server/public/filepond-plugin-image-exif-orientation.min.js +9 -0
  67. package/server/public/filepond-plugin-image-preview.min.css +8 -0
  68. package/server/public/filepond-plugin-image-preview.min.js +9 -0
  69. package/server/public/filepond-plugin-image-resize.min.js +9 -0
  70. package/server/public/filepond-plugin-image-transform.min.js +9 -0
  71. package/server/public/filepond.min.css +8 -0
  72. package/server/public/filepond.min.js +9 -0
  73. package/server/public/fscreator.js +174 -0
  74. package/server/public/fseditor.js +212 -0
  75. package/server/public/github.png +0 -0
  76. package/server/public/highlight-js.js +81 -0
  77. package/server/public/highlight.css +9 -0
  78. package/server/public/highlight.js +1244 -0
  79. package/server/public/install.js +17 -20
  80. package/server/public/modalinput.js +105 -9
  81. package/server/public/opener.js +3 -1
  82. package/server/public/pinokio-black.png +0 -0
  83. package/server/public/pinokio-black2.png +0 -0
  84. package/server/public/redoc.standalone.js +1832 -0
  85. package/server/public/style.css +211 -17
  86. package/server/public/swagger-ui-bundle.js +2 -0
  87. package/server/public/swagger-ui.css +3 -0
  88. package/server/public/x.js +63 -0
  89. package/server/socket.js +136 -39
  90. package/server/views/404.ejs +0 -1
  91. package/server/views/app.ejs +356 -90
  92. package/server/views/bootstrap.ejs +22 -19
  93. package/server/views/connect/x.ejs +324 -0
  94. package/server/views/connect.ejs +197 -0
  95. package/server/views/download.ejs +7 -1
  96. package/server/views/editor.ejs +68 -9
  97. package/server/views/explore.ejs +13 -10
  98. package/server/views/file_explorer.ejs +10 -37
  99. package/server/views/github.ejs +218 -0
  100. package/server/views/help.ejs +4 -3
  101. package/server/views/index.ejs +268 -118
  102. package/server/views/install.ejs +56 -29
  103. package/server/views/keys.ejs +276 -0
  104. package/server/views/network.ejs +889 -0
  105. package/server/views/partials/dynamic.ejs +9 -0
  106. package/server/views/partials/menu.ejs +7 -1
  107. package/server/views/prototype/index.ejs +311 -0
  108. package/server/views/prototype/show.ejs +383 -0
  109. package/server/views/redoc.ejs +18 -0
  110. package/server/views/required_env_editor.ejs +11 -2
  111. package/server/views/settings.ejs +38 -10
  112. package/server/views/setup.ejs +254 -0
  113. package/server/views/setup_home.ejs +192 -0
  114. package/server/views/shell.ejs +781 -0
  115. package/server/views/swaggerui.ejs +19 -0
  116. package/server/views/terminal.ejs +174 -42
  117. package/LICENSE +0 -7
  118. package/server/views/proxy.ejs +0 -405
package/server/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const express = require('express');
2
+ const kill = require('kill-sync')
2
3
  const portfinder = require('portfinder-cp');
3
4
  const proxy = require('express-http-proxy-cp');
4
5
  const sudo = require("sudo-prompt-programfiles-x86");
@@ -13,12 +14,12 @@ const cors = require('cors');
13
14
  const path = require("path")
14
15
  const fs = require('fs');
15
16
  const os = require('os')
16
- const { fork } = require('child_process');
17
+ const { fork, exec } = require('child_process');
17
18
  const semver = require('semver')
18
19
  const fse = require('fs-extra')
19
20
  const QRCode = require('qrcode')
20
-
21
- //const PINOKIO_HOMEPAGE = "http://localhost:3000"
21
+ const axios = require('axios')
22
+ const crypto = require('crypto')
22
23
 
23
24
  const git = require('isomorphic-git')
24
25
  const http = require('isomorphic-git/http/node')
@@ -29,6 +30,8 @@ const multer = require('multer');
29
30
 
30
31
  const ejs = require('ejs');
31
32
 
33
+ const DEFAULT_PORT = 42000
34
+
32
35
  const ex = fn => (req, res, next) => {
33
36
  Promise.resolve(fn(req, res, next)).catch(next);
34
37
  };
@@ -42,11 +45,15 @@ const packagejson = require("../package.json")
42
45
  const Environment = require("../kernel/environment")
43
46
  const Cloudflare = require("../kernel/api/cloudflare")
44
47
  const Util = require("../kernel/util")
48
+
49
+ const Setup = require("../kernel/bin/setup")
50
+
45
51
  class Server {
46
52
  constructor(config) {
47
53
  this.tabs = {}
48
54
  this.agent = config.agent
49
- this.port = config.port
55
+ this.port = DEFAULT_PORT
56
+ // this.port = config.port
50
57
  this.kernel = new Kernel(config.store)
51
58
  // this.tunnels = {}
52
59
  this.version = {
@@ -93,29 +100,48 @@ class Server {
93
100
  exists (s) {
94
101
  return new Promise(r=>fs.access(s, fs.constants.F_OK, e => r(!e)))
95
102
  }
96
- winBuildNumber() {
97
- let osVersion = (/(\d+)\.(\d+)\.(\d+)/g).exec(os.release());
98
- let buildNumber = 0;
99
- if (osVersion && osVersion.length === 4) {
100
- buildNumber = parseInt(osVersion[3]);
103
+ async updateMeta(formData, app_path) {
104
+ console.log("updateMeta", app_path)
105
+ if (formData.icon_dirty) {
106
+ // write icon file
107
+ let icon_path = this.kernel.path("api", formData.new_path, formData.icon_path)
108
+ console.log({ icon_path })
109
+ await fs.promises.writeFile(icon_path, formData.avatar)
101
110
  }
102
- return buildNumber;
103
- }
104
- compatible() {
105
- if (this.kernel.platform === "win32") {
106
- let buildNumber = this.winBuildNumber()
107
- if (buildNumber < 18309) {
108
- // must use conpty for node-pty, and conpty is only supported in win>=18309
109
- console.log("Windows buildNumber", buildNumber)
110
- throw new Error(`Pinokio supports Windows release 18309 and up (current system: ${buildNumber}`)
111
- }
112
111
 
113
- // if (buildNumber > 25000) {
114
- // console.log("Windows buildNumber", buildNumber)
115
- // throw new Error(`Pinokio does not currently support Windows Canary (versions 25000 and up). The current system is ${buildNumber}`)
116
- //
117
- // }
112
+ // write title/description to pinokio.json
113
+ let dirty
114
+ let meta_path = this.kernel.path("api", app_path, "pinokio.json")
115
+ console.log("meta_path", meta_path)
116
+ let meta = (await this.kernel.loader.load(meta_path)).resolved
117
+ console.log("meta", meta)
118
+ if (!meta) meta = {}
119
+ if (formData.title) {
120
+ meta.title = formData.title
121
+ dirty = true
118
122
  }
123
+ if (formData.description) {
124
+ meta.description = formData.description
125
+ dirty = true
126
+ }
127
+ if (dirty) {
128
+ await fs.promises.writeFile(meta_path, JSON.stringify(meta, null, 2))
129
+ }
130
+ }
131
+ async createMeta(formData) {
132
+ let _path = this.kernel.path("api", formData.path)
133
+ await fs.promises.mkdir(_path, { recursive: true }).catch((e) => {})
134
+ let icon_path = this.kernel.path("api", formData.path, "icon.png")
135
+ await fs.promises.writeFile(icon_path, formData.avatar)
136
+
137
+ // write title/description to pinokio.json
138
+ let meta_path = this.kernel.path("api", formData.path, "pinokio.json")
139
+ let meta = {
140
+ title: formData.title,
141
+ description: formData.description,
142
+ icon: "icon.png"
143
+ }
144
+ await fs.promises.writeFile(meta_path, JSON.stringify(meta, null, 2))
119
145
  }
120
146
  getMemory(filepath) {
121
147
  let localMem = this.kernel.memory.local[filepath]
@@ -152,8 +178,10 @@ class Server {
152
178
  return items.map((x) => {
153
179
  let name
154
180
  let description
155
- let icon
181
+ let icon = "/pinokio-black.png"
156
182
  let uri
183
+ let iconpath
184
+ let apipath
157
185
  if (meta) {
158
186
  let m = meta[x.name]
159
187
  name = (m && m.title ? m.title : x.name)
@@ -161,7 +189,14 @@ class Server {
161
189
  if (m && m.icon) {
162
190
  icon = m.icon
163
191
  } else {
164
- icon = null
192
+ icon = "/pinokio-black.png"
193
+ //icon = null
194
+ }
195
+ if (m && m.iconpath) {
196
+ iconpath = m.iconpath
197
+ }
198
+ if (m && m.path) {
199
+ apipath = m.path
165
200
  }
166
201
  uri = x.name
167
202
  } else {
@@ -182,6 +217,8 @@ class Server {
182
217
  let browser_browse_url = browser_url + "/browse"
183
218
  return {
184
219
  icon,
220
+ iconpath,
221
+ path: apipath,
185
222
  running: x.running ? true : false,
186
223
  run: x.run,
187
224
  menu: x.menu,
@@ -232,9 +269,7 @@ class Server {
232
269
  }
233
270
  async chrome(req, res, type) {
234
271
  let name = req.params.name
235
- let app_path = this.kernel.path("api", name, "pinokio.js")
236
- let rawpath = "/api/" + name
237
- let config = (await this.kernel.loader.load(app_path)).resolved
272
+ let config = await this.kernel.api.meta(name)
238
273
 
239
274
  let error = null
240
275
  if (config && config.version) {
@@ -293,8 +328,12 @@ class Server {
293
328
  let files = await fs.promises.readdir(p, { withFileTypes: true })
294
329
  files = files.filter((file) => {
295
330
  return file.name.endsWith(".json") || file.name.endsWith(".js")
331
+ }).filter((file) => {
332
+ return file.name !== "pinokio.js" && file.name !== "pinokio.json" && file.name !== "pinokio_meta.json"
296
333
  })
334
+ console.log("files", files)
297
335
  config = {
336
+ icon: config.icon,
298
337
  title: name,
299
338
  menu: files.map((file) => {
300
339
  return {
@@ -307,6 +346,20 @@ class Server {
307
346
  await this.renderMenu(uri, name, config, [])
308
347
  }
309
348
  let platform = os.platform()
349
+ console.log("config before", { config })
350
+
351
+ // if (config.icon) {
352
+ // //config.iconpath = this.kernel.path("api", name, config.icon)
353
+ // config.iconpath = config.icon
354
+ // config.icon = `${rawpath}/${config.icon}?raw=true`
355
+ // } else {
356
+ // //config.iconpath = this.kernel.path("api", name, "pinokio_icon.png")
357
+ // config.iconpath = "pinokio.png"
358
+ // config.icon = "/pinokio-black.png"
359
+ // }
360
+
361
+ console.log("config after", { config })
362
+
310
363
 
311
364
 
312
365
  // get all memory variable stied to the current repository
@@ -330,7 +383,6 @@ class Server {
330
383
 
331
384
  await this.init_env("api/" + name)
332
385
 
333
-
334
386
  let mode = "run"
335
387
  if (req.query && req.query.mode) {
336
388
  mode = req.query.mode
@@ -339,6 +391,16 @@ class Server {
339
391
 
340
392
  // profile + feed
341
393
  const repositoryPath = path.resolve(this.kernel.api.userdir, name)
394
+
395
+ try {
396
+ await git.resolveRef({ fs, dir: repositoryPath, ref: 'HEAD' });
397
+ } catch (err) {
398
+ // repo doesn't exist. initialize.
399
+ console.log(`repo doesn't exist at ${repositoryPath}. initialize`)
400
+ await git.init({ fs, dir: repositoryPath });
401
+ }
402
+
403
+
342
404
  let gitRemote = await git.getConfig({ fs, http, dir: repositoryPath, path: 'remote.origin.url' })
343
405
  let profile
344
406
  let feed
@@ -353,7 +415,13 @@ class Server {
353
415
  feed = this.newsfeed(gitRemote)
354
416
  }
355
417
 
418
+ let plugin = await this.getPlugin(name)
419
+ let plugin_menu = null
420
+ if (plugin && plugin.menu && Array.isArray(plugin.menu)) {
421
+ plugin_menu = plugin.menu
422
+ }
356
423
  res.render("app", {
424
+ plugin_menu,
357
425
  portal: this.portal,
358
426
  install: this.install,
359
427
  error,
@@ -366,6 +434,9 @@ class Server {
366
434
  running:this.kernel.api.running,
367
435
  memory: this.kernel.memory,
368
436
  sidebar: "/pinokio/sidebar/" + name,
437
+ dynamic: "/pinokio/dynamic/" + name,
438
+ // dynamic: "/pinokio/dynamic/" + name,
439
+ dynamic_content: null,
369
440
  name,
370
441
  profile,
371
442
  feed,
@@ -378,13 +449,27 @@ class Server {
378
449
  agent: this.agent,
379
450
  src: "/_api/" + name,
380
451
  execUrl: "/api/" + name,
381
- rawpath,
452
+ // rawpath,
382
453
  })
383
454
  }
384
- async render(req, res, pathComponents, meta) {
455
+ getVariationUrls(req) {
456
+ let edu = new URL("http://localhost" + req.originalUrl)
457
+ edu.searchParams.set("mode", "source")
458
+ let editorUrl = edu.pathname + edu.search
385
459
 
386
-
387
- let full_filepath = this.kernel.path("api", ...pathComponents)
460
+ let referer = req.get("Referer")
461
+ let prevUrl = null
462
+ try {
463
+ if (/\/env\/api\/.+/.test(new URL(referer).pathname)) {
464
+ prevUrl = referer
465
+ }
466
+ } catch (e) {
467
+ }
468
+ return { editorUrl, prevUrl }
469
+ }
470
+ async render(req, res, pathComponents, meta) {
471
+ let base_path = req.base || this.kernel.path("api")
472
+ let full_filepath = path.resolve(base_path, ...pathComponents)
388
473
 
389
474
  let re = /^(.+\..+)(#.*)$/
390
475
  let match = re.exec(full_filepath)
@@ -512,85 +597,9 @@ class Server {
512
597
  display: ["form"]
513
598
  })
514
599
  } else if (pathComponents.length === 0 && req.query.mode === "download") {
515
-
516
- let requirements = [{
517
- name: "conda",
518
- }, {
519
- name: "git",
520
- }, {
521
- name: "zip",
522
- }, {
523
- name: "node",
524
- }, {
525
- name: "ffmpeg",
526
- // type: "conda",
527
- // name: "ffmpeg",
528
- // args: "-c conda-forge"
529
- }]
530
- let platform = os.platform()
531
- if (platform === "win32") {
532
- requirements.push({
533
- name: "registry"
534
- })
535
- requirements.push({
536
- name: "vs"
537
- })
538
- }
539
- if (platform === "darwin") {
540
- requirements.push({
541
- name: "brew"
542
- })
543
- }
544
- if (platform === "linux") {
545
- requirements.push({
546
- name: "gxx"
547
- })
548
- }
549
- if (this.kernel.gpu === "nvidia") {
550
- requirements.push({
551
- name: "cuda",
552
- })
553
- }
554
- requirements = requirements.concat([{
555
- name: "py"
556
- }, {
557
- name: "cloudflared"
558
- }, {
559
- name: "playwright"
560
- }, {
561
- name: "huggingface"
562
- }, {
563
- name: "uv"
564
- }])
565
-
566
- let requirements_pending = !this.kernel.bin.installed_initialized
567
-
568
-
569
- let install_required = true
570
- if (!requirements_pending) {
571
- install_required = false
572
- for(let i=0; i<requirements.length; i++) {
573
- let r = requirements[i]
574
- console.time(r.name)
575
- let installed = await this.installed(r)
576
- console.log({ r, installed })
577
- console.timeEnd(r.name)
578
- requirements[i].installed = installed
579
- if (!installed) {
580
- install_required = true
581
- }
582
- }
583
- }
584
-
585
- let error = null
586
- try {
587
- this.compatible()
588
- } catch (e) {
589
- error = e.message
590
- install_required = true
591
- }
592
-
593
-
600
+ let { requirements, install_required, requirements_pending, error } = await this.kernel.bin.check({
601
+ bin: this.kernel.bin.preset("ai"),
602
+ })
594
603
  res.render("download", {
595
604
  error,
596
605
  current: req.originalUrl,
@@ -649,7 +658,6 @@ class Server {
649
658
  drive: path.resolve(this.kernel.homedir, "drive"),
650
659
  }
651
660
  }
652
- console.log({ configArray })
653
661
  res.render("settings", {
654
662
  platform,
655
663
  version: this.version,
@@ -742,7 +750,10 @@ class Server {
742
750
  //} else {
743
751
  // uri = path.resolve(this.kernel.api.userdir, ...pathComponents)
744
752
  //}
745
- uri = path.resolve(this.kernel.api.userdir, ...pathComponents)
753
+
754
+
755
+ //uri = path.resolve(this.kernel.api.userdir, ...pathComponents)
756
+ uri = full_filepath
746
757
 
747
758
  let pinokioPath
748
759
  if (gitRemote) {
@@ -770,6 +781,7 @@ class Server {
770
781
  let stem = filename.replace(/\.(json|js)$/, "")
771
782
  let stempath = pathComponents.slice(0,-1).join("/") + "/_" + stem
772
783
  for(let p of [stempath + ".json", stempath + ".js"]) {
784
+ //const schemaFullPath = path.resolve(this.kernel.api.userdir, p)
773
785
  const schemaFullPath = path.resolve(this.kernel.api.userdir, p)
774
786
  let exists = await this.exists(schemaFullPath)
775
787
  if (exists) {
@@ -796,227 +808,59 @@ class Server {
796
808
  resolved = runner
797
809
  }
798
810
 
799
- let template
800
- template = "terminal"
801
-
811
+ let template = "terminal"
802
812
  if (req.query && req.query.mode === "source") {
803
813
  template = "editor"
804
814
  }
805
815
 
816
+ console.log("check requirements")
817
+ let { requirements, install_required, requirements_pending, error } = await this.kernel.bin.check({
818
+ bin: this.kernel.bin.preset("ai"),
819
+ script: resolved
820
+ })
821
+ console.log({ requirements })
822
+
823
+ //let requirements = this.kernel.bin.requirements(resolved)
824
+ //let requirements_pending = !this.kernel.bin.installed_initialized
825
+ //let install_required = true
826
+ //if (!requirements_pending) {
827
+ // install_required = false
828
+ // for(let i=0; i<requirements.length; i++) {
829
+ // let r = requirements[i]
830
+
831
+ // let relevant = this.relevant(r)
832
+ // requirements[i].relevant = relevant
833
+ // if (relevant) {
834
+ // let installed = await this.installed(r)
835
+ // requirements[i].installed = installed
836
+ // if (!installed) {
837
+ // install_required = true
838
+ // }
839
+ // }
840
+ // }
841
+ //}
806
842
 
807
- // Deprecate source view and form view => Everything is full screen by default
808
- // if (req.query && req.query.mode === "source") {
809
- // if (req.query && req.query.fullscreen) {
810
- // template = "fullscreen_editor"
811
- // } else {
812
- // template = "editor"
813
- // }
814
- // } else if (schemaPath && schemaPath.length > 0) {
815
- // template = "form"
816
- // } else {
817
- // if (req.query && req.query.fullscreen) {
818
- // template = "fullscreen_editor"
819
- // } else {
820
- // template = "editor"
821
- // }
822
- // }
823
-
824
-
825
- let requirements = [{
826
- name: "conda",
827
- }, {
828
- name: "git",
829
- }, {
830
- name: "zip",
831
- }, {
832
- name: "node",
833
- }, {
834
- name: "ffmpeg",
835
- // type: "conda",
836
- // name: "ffmpeg",
837
- // args: "-c conda-forge"
838
- }]
839
- let platform = os.platform()
840
- if (platform === "win32") {
841
- requirements.push({
842
- name: "registry"
843
- })
844
- requirements.push({
845
- name: "vs"
846
- })
847
- }
848
- if (platform === "darwin") {
849
- requirements.push({
850
- name: "brew"
851
- })
852
- }
853
- if (platform === "linux") {
854
- requirements.push({
855
- name: "gxx"
856
- })
857
- }
858
- if (this.kernel.gpu === "nvidia") {
859
- requirements.push({
860
- name: "cuda"
861
- })
862
- }
863
- requirements = requirements.concat([{
864
- name: "py"
865
- }, {
866
- name: "cloudflared"
867
- }, {
868
- name: "playwright"
869
- }, {
870
- name: "huggingface"
871
- }, {
872
- name: "uv"
873
- }])
874
- // if (platform === "linux") {
875
- // requirements.push({
876
- // name: "brew"
877
- // })
878
- // }
879
-
880
- if (resolved && resolved.requires && resolved.requires.length > 0) {
881
- /*********************************************************************
882
-
883
- syntax :=
884
-
885
- {
886
- platform: <win32|darwin|linux>,
887
- type: <conda|pip|brew|none>,
888
- name: <package name>, (example: "ffmpeg", "git")
889
- args: <install command flags> (example: "-c conda-forge")
890
- }
891
-
892
-
893
- 1. pinokio native install: no need for specifying platforms since they are included
894
-
895
- {
896
- name: "conda"
897
- }
898
-
899
-
900
- 2. non native install (conda, pip, brew)
901
-
902
- 2.1. Same on all platforms
903
-
904
- [{
905
- type: "conda",
906
- name: "ffmpeg",
907
- args: "-c conda-forge"
908
- }]
909
-
910
- 2.2. Specify per platform
911
-
912
-
913
- [
914
- { name: "conda" },
915
- { platform: "darwin", type: "brew", name: "llvm" },
916
- { platform: "linux", tyoe: "conda", name: "llvm", args: "-c conda-forge llvm" },
917
- { platform: "win32", tyoe: "conda", name: "llvm", args: "-c conda-forge llvm" }
918
- ]
919
-
920
- [
921
- { name: "conda" },
922
- { platform: ["darwin", "linux"], type: "brew", name: "llvm" },
923
- { platform: "win32", tyoe: "conda", name: "llvm", args: "-c conda-forge llvm" }
924
- ]
925
-
926
-
927
-
928
- *********************************************************************/
929
-
930
-
931
- let platform = os.platform()
932
- let type_name_set = new Set()
933
- for(let r of resolved.requires) {
934
- // if no platform specified, or if the specified platform matches the current platform
935
- if (!r.platform || platform === r.platform || Array.isArray(r.platform) && r.platform.includes(platform) ) {
936
- if (Array.isArray(r.name)) {
937
- // if array, just add it
938
- requirements.push(r)
939
- } else {
940
- let type_name = `${r.type ? r.type : ''}/${r.name}`
941
- if (!type_name_set.has(type_name)) {
942
- type_name_set.add(type_name)
943
- requirements.push(r)
944
- }
945
- }
946
- }
947
- }
948
-
949
- }
950
- let requirements_pending = !this.kernel.bin.installed_initialized
951
-
952
- let install_required = true
953
- if (!requirements_pending) {
954
- install_required = false
955
- console.time(">>>>>>>>>> 1")
956
- for(let i=0; i<requirements.length; i++) {
957
- let r = requirements[i]
958
-
959
- let relevant = this.relevant(r)
960
- requirements[i].relevant = relevant
961
- if (relevant) {
962
- let installed = await this.installed(r)
963
- requirements[i].installed = installed
964
- if (!installed) {
965
- install_required = true
966
- }
967
- }
968
- }
969
- console.timeEnd(">>>>>>>>>> 1")
970
- }
971
-
972
- let error = null
973
- try {
974
- this.compatible()
975
- } catch (e) {
976
- error = e.message
977
- install_required = true
978
- }
843
+ //let error = null
844
+ //try {
845
+ // this.kernel.bin.compatible()
846
+ //} catch (e) {
847
+ // error = e.message
848
+ // install_required = true
849
+ //}
979
850
 
980
- requirements = requirements.filter((r) => {
981
- return r.relevant
982
- })
851
+ //requirements = requirements.filter((r) => {
852
+ // return r.relevant
853
+ //})
983
854
 
984
855
 
985
856
  let mem = this.getMemory(filepath)
986
- let edu = new URL("http://localhost" + req.originalUrl)
987
- edu.searchParams.set("mode", "source")
988
- let editorUrl = edu.pathname + edu.search
989
-
990
- let referer = req.get("Referer")
991
857
 
992
- let prev = null
993
- try {
994
- if (/\/env\/api\/.+/.test(new URL(referer).pathname)) {
995
- prev = referer
996
- }
997
- } catch (e) {
998
- }
858
+ let { editorUrl, prevUrl } = this.getVariationUrls(req)
999
859
 
1000
860
 
1001
- let requires_instantiation = false
1002
- let pre_items = []
1003
- if (resolved && resolved.pre) {
1004
- let env = await Environment.get2(filepath, this.kernel)
1005
- for(let item of resolved.pre) {
1006
- if (item.env) {
1007
- if (env[item.env]) {
1008
- item.val = env[item.env]
1009
- } else {
1010
- if (item.default) {
1011
- item.val = item.default
1012
- }
1013
- requires_instantiation = true
1014
- }
1015
- pre_items.push(item)
1016
- }
1017
- }
1018
- }
1019
- if (requires_instantiation) {
861
+ let env_requirements = await Environment.requirements(resolved, filepath, this.kernel)
862
+ console.log({ env_requirements })
863
+ if (env_requirements.requires_instantiation) {
1020
864
  let p = Util.api_path(filepath, this.kernel)
1021
865
  let platform = os.platform()
1022
866
  if (platform === "win32") {
@@ -1028,14 +872,24 @@ class Server {
1028
872
  theme: this.theme,
1029
873
  filename,
1030
874
  filepath: p,
1031
- items: pre_items
875
+ items: env_requirements.items
1032
876
  })
1033
877
  } else {
878
+
879
+ // check if it's a prototype script
880
+ let kill_message
881
+ let callback
882
+ if (req.query.callback) {
883
+ callback = req.query.callback
884
+ // kill_message = "Done! Click to go to the project"
885
+ }
886
+
1034
887
  let logpath = encodeURIComponent(Util.log_path(filepath, this.kernel))
1035
- console.log({ logpath })
1036
888
  const result = {
1037
889
  portal: this.portal,
1038
- prev,
890
+ kill_message,
891
+ callback,
892
+ prev: prevUrl,
1039
893
  error,
1040
894
  memory: mem,
1041
895
  // memory: mem,
@@ -1069,6 +923,8 @@ class Server {
1069
923
  editorUrl,
1070
924
  execUrl: "~" + req.originalUrl.replace(/^\/_api/, "\/api"),
1071
925
  proxies: this.kernel.api.proxies[filepath],
926
+ cwd: req.query.cwd,
927
+ script_path: (req.base ? full_filepath : null),
1072
928
  }
1073
929
 
1074
930
  res.render(template, result)
@@ -1178,6 +1034,24 @@ class Server {
1178
1034
  config.update = "/api/" + link
1179
1035
  }
1180
1036
  }
1037
+
1038
+ // override config
1039
+ if (file.name === "pinokio_meta.json" || file.name === "pinokio.json") {
1040
+ let p = path.resolve(filepath, file.name)
1041
+ let c = (await this.kernel.loader.load(p)).resolved
1042
+ if (c.title) {
1043
+ if (!config) config = {}
1044
+ config.title = c.title
1045
+ }
1046
+ if (c.description) {
1047
+ if (!config) config = {}
1048
+ config.description = c.description
1049
+ }
1050
+ if (c.icon) {
1051
+ if (!config) config = {}
1052
+ config.icon = c.icon
1053
+ }
1054
+ }
1181
1055
  }
1182
1056
  if (!config) config = {}
1183
1057
 
@@ -1232,7 +1106,6 @@ class Server {
1232
1106
  pinokioPath = `pinokio://?uri=${gitRemote}/${pathComponents.slice(1).join("/")}`
1233
1107
  }
1234
1108
 
1235
-
1236
1109
  let running = []
1237
1110
  let notRunning = []
1238
1111
  if (pathComponents.length === 0) {
@@ -1314,7 +1187,18 @@ class Server {
1314
1187
  }
1315
1188
  index++;
1316
1189
  running.push(items[i])
1317
- break
1190
+ // break
1191
+ }
1192
+ let shell = this.kernel.shell.find({
1193
+ filter: (shell) => {
1194
+ return shell.id.startsWith(items[i].name + "_")
1195
+ }
1196
+ })
1197
+ if (shell.length > 0) {
1198
+ items[i].running = true
1199
+ items[i].index = index
1200
+ index++;
1201
+ running.push(items[i])
1318
1202
  }
1319
1203
  }
1320
1204
  if (!items[i].running) {
@@ -1336,7 +1220,9 @@ class Server {
1336
1220
  //let name = (x.name.startsWith("0x") ? Buffer.from(x.name.slice(2), "hex").toString() : x.name)
1337
1221
  let name
1338
1222
  let description
1339
- let icon
1223
+ let icon = "/pinokio-black.png"
1224
+ let iconpath
1225
+ let apipath
1340
1226
  let uri
1341
1227
  if (meta) {
1342
1228
  let m = meta[x.name]
@@ -1345,7 +1231,14 @@ class Server {
1345
1231
  if (m && m.icon) {
1346
1232
  icon = m.icon
1347
1233
  } else {
1348
- icon = null
1234
+ //icon = null
1235
+ icon = "/pinokio-black.png"
1236
+ }
1237
+ if (m && m.iconpath) {
1238
+ iconpath = m.iconpath
1239
+ }
1240
+ if (m && m.path) {
1241
+ apipath = m.path
1349
1242
  }
1350
1243
  uri = x.name
1351
1244
  } else {
@@ -1359,6 +1252,8 @@ class Server {
1359
1252
  }
1360
1253
  return {
1361
1254
  icon,
1255
+ iconpath,
1256
+ path: apipath,
1362
1257
  menu: x.menu,
1363
1258
  run: x.run,
1364
1259
  shortcuts: x.shortcuts,
@@ -1374,6 +1269,7 @@ class Server {
1374
1269
  }
1375
1270
  })
1376
1271
 
1272
+
1377
1273
  // if (req.query && req.query.mode === "task") {
1378
1274
  // running = running.filter((x) => {
1379
1275
  // return x.run && Array.isArray(x.run)
@@ -1422,7 +1318,6 @@ class Server {
1422
1318
  }
1423
1319
  if (config.xterm) {
1424
1320
  this.xterm = config.xterm
1425
- console.log("this.xterm", this.xterm)
1426
1321
  }
1427
1322
  }
1428
1323
  }
@@ -1433,6 +1328,7 @@ class Server {
1433
1328
  res.render("index", {
1434
1329
  portal: this.portal,
1435
1330
  install: this.install,
1331
+ folders: null,
1436
1332
  launch_complete: this.kernel.launch_complete,
1437
1333
  home_url: `http://localhost:${this.port}`,
1438
1334
  proxy: home_proxy,
@@ -1571,6 +1467,9 @@ class Server {
1571
1467
  if (menuitem.fs) {
1572
1468
  // file explorer
1573
1469
  config.menu[i].href = path.resolve(this.kernel.homedir, "api", name, menuitem.href)
1470
+ } else if (menuitem.command) {
1471
+ // file explorer
1472
+ config.menu[i].href = path.resolve(this.kernel.homedir, "api", name, menuitem.href)
1574
1473
  } else {
1575
1474
  let absolute = path.resolve(__dirname, ...pathComponents, menuitem.href)
1576
1475
  let seed = path.resolve(__dirname)
@@ -1585,6 +1484,69 @@ class Server {
1585
1484
  menuitem.href = menuitem.href + "?" + new URLSearchParams(menuitem.params).toString();
1586
1485
  }
1587
1486
 
1487
+
1488
+ if (menuitem.shell) {
1489
+ /*
1490
+ shell :- {
1491
+ id (optional),
1492
+ path (required), // api, bin, quick, network, api/
1493
+ message (optional), // if not specified, start an empty shell
1494
+ venv,
1495
+ input, // input mode if true
1496
+ callback, // callback url after shutting down
1497
+ kill, // when to kill (regular expression)
1498
+ }
1499
+ */
1500
+
1501
+ let rendered = this.kernel.template.render(menuitem.shell, {})
1502
+ let params = new URLSearchParams()
1503
+ // if (rendered.id) {
1504
+ // params.set("id", encodeURIComponent(rendered.id))
1505
+ // } else {
1506
+ // let shell_id = "sh_" + name + "_" + i
1507
+ // params.set("id", encodeURIComponent(shell_id))
1508
+ // }
1509
+ let basePath = this.kernel.path("api", name)
1510
+ if (rendered.path) {
1511
+ params.set("path", encodeURIComponent(this.kernel.api.filePath(rendered.path, basePath)))
1512
+ } else {
1513
+ params.set("path", encodeURIComponent(basePath))
1514
+ }
1515
+ if (rendered.message) params.set("message", encodeURIComponent(rendered.message))
1516
+ if (rendered.venv) params.set("venv", encodeURIComponent(rendered.venv))
1517
+ if (rendered.input) params.set("input", true)
1518
+ if (rendered.callback) params.set("callback", encodeURIComponent(rendered.callback))
1519
+ if (rendered.kill) params.set("kill", encodeURIComponent(rendered.kill))
1520
+ if (rendered.done) params.set("done", encodeURIComponent(rendered.done))
1521
+ if (rendered.env) {
1522
+ for(let key in rendered.env) {
1523
+ let env_key = "env." + key
1524
+ params.set(env_key, rendered.env[key])
1525
+ }
1526
+ }
1527
+ if (rendered.conda) {
1528
+ for(let key in rendered.conda) {
1529
+ let conda_key = "conda." + key
1530
+ params.set(conda_key, rendered.conda[key])
1531
+ }
1532
+ }
1533
+
1534
+ // deterministic shell id generation
1535
+ // `${api_path}_${i}_${hash}`
1536
+ let hash = crypto.createHash('md5').update(JSON.stringify(rendered)).digest('hex')
1537
+ let shell_id
1538
+ if (rendered.id) {
1539
+ shell_id = encodeURIComponent(`${name}_${rendered.id}`)
1540
+ } else {
1541
+ shell_id = encodeURIComponent(`${name}_${i}_${hash}`)
1542
+ }
1543
+ menuitem.href = "/shell/" + shell_id + "?" + params.toString()
1544
+ let shell = this.kernel.shell.get(shell_id)
1545
+ if (shell) {
1546
+ menuitem.running = true
1547
+ }
1548
+ }
1549
+
1588
1550
  if (menuitem.href) {
1589
1551
  let u
1590
1552
  if (menuitem.href.startsWith("http")) {
@@ -1744,95 +1706,20 @@ class Server {
1744
1706
  //
1745
1707
  // console.log("MENU", JSON.stringify(config.menu, null, 2))
1746
1708
 
1709
+ // if (!config.icon) {
1710
+ // if (this.theme === "light") {
1711
+ // config.icon = "/pinokio-black.png"
1712
+ // } else {
1713
+ // config.icon = "/pinokio-white.png"
1714
+ // }
1715
+ // }
1716
+ // console.log("############## config", config)
1747
1717
 
1748
1718
  return config
1749
1719
  } else {
1750
1720
  return config
1751
1721
  }
1752
1722
  }
1753
- relevant(r) {
1754
- /*
1755
- single platform
1756
- [
1757
- { name: "conda" },
1758
- { platform: "darwin", type: "brew", name: "llvm" },
1759
- { platform: "linux", tyoe: "conda", name: "llvm", args: "-c conda-forge llvm" },
1760
- { platform: "win32", tyoe: "conda", name: "llvm", args: "-c conda-forge llvm" }
1761
- ]
1762
-
1763
- multiple platforms
1764
- [
1765
- { name: "conda" },
1766
- { platform: ["darwin", "linux"], type: "brew", name: "llvm" },
1767
- { platform: "win32", tyoe: "conda", name: "llvm", args: "-c conda-forge llvm" }
1768
- ]
1769
-
1770
-
1771
- platform & arch
1772
- [
1773
- { name: "conda" },
1774
- { platform: "darwin", arch: ["arm64", "x64"], type: "brew", name: "llvm" },
1775
- ]
1776
- */
1777
-
1778
- let platform = os.platform()
1779
- let arch = os.arch()
1780
- let gpu = this.kernel.gpu
1781
- let relevant = {
1782
- platform: false,
1783
- arch: false,
1784
- gpu: false,
1785
- }
1786
- if (r.platform) {
1787
- if (Array.isArray(r.platform)) {
1788
- // multiple items
1789
- if (r.platform.includes(platform)) {
1790
- relevant.platform = true
1791
- }
1792
- } else {
1793
- // one item
1794
- if (r.platform === platform) {
1795
- relevant.platform = true
1796
- }
1797
- }
1798
- } else {
1799
- // all platforms
1800
- relevant.platform = true
1801
- }
1802
- if (r.arch) {
1803
- if (Array.isArray(r.arch)) {
1804
- // multiple items
1805
- if (r.arch.includes(arch)) {
1806
- relevant.arch = true
1807
- }
1808
- } else {
1809
- // one item
1810
- if (r.arch === arch) {
1811
- relevant.arch = true
1812
- }
1813
- }
1814
- } else {
1815
- // all platforms
1816
- relevant.arch = true
1817
- }
1818
- if (r.gpu) {
1819
- if (Array.isArray(r.gpu)) {
1820
- // multiple items
1821
- if (r.gpu.includes(gpu)) {
1822
- relevant.gpu = true
1823
- }
1824
- } else {
1825
- // one item
1826
- if (r.gpu === gpu) {
1827
- relevant.gpu = true
1828
- }
1829
- }
1830
- } else {
1831
- // all platforms
1832
- relevant.gpu = true
1833
- }
1834
- return relevant.platform && relevant.arch && relevant.gpu
1835
- }
1836
1723
  async _installed(name, type) {
1837
1724
  if (type === "conda") {
1838
1725
  return this.kernel.bin.installed.conda.has(name)
@@ -1864,7 +1751,6 @@ class Server {
1864
1751
  }
1865
1752
  }
1866
1753
  async sudo_exec(message, homedir) {
1867
- console.log("sudo_exec", { message, homedir })
1868
1754
  // sudo-prompt uses TEMP
1869
1755
  // let TEMP = path.resolve(homedir, "cache", "TEMP")
1870
1756
  // await fs.promises.mkdir(TEMP, { recursive: true }).catch((e) => { })
@@ -1922,6 +1808,85 @@ class Server {
1922
1808
  }
1923
1809
  console.log("FINISHED MV")
1924
1810
  }
1811
+ getPeerInfo() {
1812
+ let list = []
1813
+ let peers_info = {}
1814
+ if (this.kernel.peer.info) {
1815
+ peers_info = this.kernel.peer.info
1816
+ let remote_peers = Object.keys(this.kernel.peer.info).filter(x => x !== this.kernel.peer.host)
1817
+ let nodes = [this.kernel.peer.host].concat(remote_peers)
1818
+ for(let host of nodes) {
1819
+ let processes = []
1820
+ try {
1821
+ let procs = this.kernel.peer.info[host].proc
1822
+ let router = this.kernel.peer.info[host].router
1823
+ let port_mapping = this.kernel.peer.info[host].port_mapping
1824
+ for(let proc of procs) {
1825
+ let chunks = proc.ip.split(":")
1826
+ let internal_port = chunks[chunks.length-1]
1827
+ let internal_host = chunks.slice(0, chunks.length-1).join(":")
1828
+ let external_port = port_mapping[internal_port]
1829
+
1830
+ let merged
1831
+ let external_ip
1832
+ if (external_port) {
1833
+ external_ip = `${host}:${external_port}`
1834
+ // merged = Array.from(new Set(router[external_ip].concat(router[proc.ip])))
1835
+ } else {
1836
+ // merged = router[proc.ip]
1837
+ }
1838
+ processes.push({
1839
+ external_router: router[external_ip] || [],
1840
+ internal_router: router[proc.ip] || [],
1841
+ //router: merged || [],
1842
+ external_ip,
1843
+ external_port: parseInt(external_port),
1844
+ internal_port: parseInt(internal_port),
1845
+ ...proc
1846
+ })
1847
+ //if (external_port) {
1848
+ // let external_ip = `${host}:${external_port}`
1849
+ // // merge router
1850
+ // let merged = Array.from(new Set(router[external_ip].concat(router[proc.ip))
1851
+ // processes.push({
1852
+ // //proxy: this.kernel.caddy.mapping[item.ip] || [],
1853
+ // //router: router[external_ip] || [],
1854
+ // router: merged || [],
1855
+ // external_ip,
1856
+ // external_port: parseInt(external_port),
1857
+ // internal_port: parseInt(internal_port),
1858
+ // ...proc
1859
+ // })
1860
+ //} else {
1861
+ // processes.push({
1862
+ // router: [],
1863
+ // external_port: parseInt(external_port),
1864
+ // internal_port: parseInt(internal_port),
1865
+ // ...proc
1866
+ // })
1867
+ //}
1868
+ }
1869
+ // merge processes
1870
+ // 1.
1871
+ processes.sort((a, b) => {
1872
+ return b.external_port-a.external_port
1873
+ })
1874
+ list.push({
1875
+ host,
1876
+ name: this.kernel.peer.info[host].name,
1877
+ platform: this.kernel.peer.info[host].platform,
1878
+ processes
1879
+ })
1880
+ } catch (e) {
1881
+ console.log(">>e", e)
1882
+ console.log({ host, info: this.kernel.peer.info })
1883
+ }
1884
+ }
1885
+ // console.log("Loaded yet?", nodes.length, Object.keys(peers_info).length, nodes.length === Object.keys(peers_info).length)
1886
+ }
1887
+ return list
1888
+ }
1889
+
1925
1890
 
1926
1891
  async syncConfig() {
1927
1892
 
@@ -1948,79 +1913,78 @@ class Server {
1948
1913
  // 5. existing home is NOT set + new home is set => go through with the "home" setting procedure
1949
1914
  // 6. existing home is NOT set + new home is NOT set => don't touch anything => the homedir will be the default home
1950
1915
 
1951
- // 2. HOME
1952
- // 2.1. Check if the config includes NEW_HOME => if so,
1953
- // - move the HOME folder to NEW_HOME
1954
- // - set HOME=NEW_HOME
1955
- // - remove NEW_HOME
1956
- let existing_home = this.kernel.store.get("home")
1957
- let new_home = this.kernel.store.get("new_home")
1958
-
1959
- if (existing_home) {
1960
- let exists = await fse.pathExists(existing_home)
1961
- if (exists) {
1962
- if (new_home) {
1963
- let new_home_exists = await fse.pathExists(new_home)
1964
- if (new_home_exists) {
1965
- // - existing home is set
1966
- // - existing home exists
1967
- // - new home is set
1968
- // - new home exists already
1969
- // => delete store.new_home ==> will load at store.home
1970
- this.kernel.store.delete("new_home")
1971
- } else {
1972
- // - existing home is set
1973
- // - existing home exists
1974
- // - new home is set
1975
- // - new home does not exist
1976
- // => run mv()
1977
- // => update store.home
1978
- // => delete store.new_home
1979
- await this.mv(existing_home, new_home)
1980
- this.kernel.store.set("home", new_home)
1981
- this.kernel.store.delete("new_home")
1982
- }
1983
- } else {
1984
- // - existing home is set
1985
- // - existing home exists
1986
- // - new home is not set
1987
- // => This is most typical scenario => don't touch anything => the homedir will be the existing home
1988
- }
1989
- } else {
1990
- if (new_home) {
1991
- // - existing home is set
1992
- // - but the existing home path DOES NOT exist
1993
- // - new home is set
1994
- // => This is an invalid scenario => Just to avoid disaster, just delete store.home and delete store.new_home
1995
- // => the app will load at ~/pinokio
1996
- this.kernel.store.delete("home")
1997
- this.kernel.store.delete("new_home")
1998
- } else {
1999
- // - existing home is set
2000
- // - but the existing home path DOES NOT exist
2001
- // - new home is NOT set
2002
- // => This is an invalid scenario => just delete store.home
2003
- // => the app will load at ~/pinokio
2004
- this.kernel.store.delete("home")
2005
- }
2006
- }
2007
- } else {
2008
- if (new_home) {
2009
- // - existing home is NOT set
2010
- // - new home is set
2011
- // => update store.home
2012
- // => delete store.new_home
2013
- this.kernel.store.set("home", new_home)
2014
- this.kernel.store.delete("new_home")
2015
- } else {
2016
- // - existing home is NOT set
2017
- // - new home is NOT set
2018
- // => don't touch anything => will load at ~/pinokio
2019
- }
2020
- }
1916
+ // // 2. HOME
1917
+ // // 2.1. Check if the config includes NEW_HOME => if so,
1918
+ // // - move the HOME folder to NEW_HOME
1919
+ // // - set HOME=NEW_HOME
1920
+ // // - remove NEW_HOME
1921
+ // let existing_home = this.kernel.store.get("home")
1922
+ // let new_home = this.kernel.store.get("new_home")
1923
+ //
1924
+ // if (existing_home) {
1925
+ // let exists = await fse.pathExists(existing_home)
1926
+ // if (exists) {
1927
+ // if (new_home) {
1928
+ // let new_home_exists = await fse.pathExists(new_home)
1929
+ // if (new_home_exists) {
1930
+ // // - existing home is set
1931
+ // // - existing home exists
1932
+ // // - new home is set
1933
+ // // - new home exists already
1934
+ // // => delete store.new_home ==> will load at store.home
1935
+ // this.kernel.store.delete("new_home")
1936
+ // } else {
1937
+ // // - existing home is set
1938
+ // // - existing home exists
1939
+ // // - new home is set
1940
+ // // - new home does not exist
1941
+ // // => run mv()
1942
+ // // => update store.home
1943
+ // // => delete store.new_home
1944
+ // await this.mv(existing_home, new_home)
1945
+ // this.kernel.store.set("home", new_home)
1946
+ // this.kernel.store.delete("new_home")
1947
+ // }
1948
+ // } else {
1949
+ // // - existing home is set
1950
+ // // - existing home exists
1951
+ // // - new home is not set
1952
+ // // => This is most typical scenario => don't touch anything => the homedir will be the existing home
1953
+ // }
1954
+ // } else {
1955
+ // if (new_home) {
1956
+ // // - existing home is set
1957
+ // // - but the existing home path DOES NOT exist
1958
+ // // - new home is set
1959
+ // // => This is an invalid scenario => Just to avoid disaster, just delete store.home and delete store.new_home
1960
+ // // => the app will load at ~/pinokio
1961
+ // this.kernel.store.delete("home")
1962
+ // this.kernel.store.delete("new_home")
1963
+ // } else {
1964
+ // // - existing home is set
1965
+ // // - but the existing home path DOES NOT exist
1966
+ // // - new home is NOT set
1967
+ // // => This is an invalid scenario => just delete store.home
1968
+ // // => the app will load at ~/pinokio
1969
+ // this.kernel.store.delete("home")
1970
+ // }
1971
+ // }
1972
+ // } else {
1973
+ // if (new_home) {
1974
+ // // - existing home is NOT set
1975
+ // // - new home is set
1976
+ // // => update store.home
1977
+ // // => delete store.new_home
1978
+ // this.kernel.store.set("home", new_home)
1979
+ // this.kernel.store.delete("new_home")
1980
+ // } else {
1981
+ // // - existing home is NOT set
1982
+ // // - new home is NOT set
1983
+ // // => don't touch anything => will load at ~/pinokio
1984
+ // }
1985
+ // }
2021
1986
  }
2022
1987
  async setConfig(config) {
2023
- console.log("setConfig", config)
2024
1988
  let home = this.kernel.store.get("home")
2025
1989
  let theme = this.kernel.store.get("theme")
2026
1990
  // let drive = this.kernel.store.get("drive")
@@ -2043,13 +2007,14 @@ class Server {
2043
2007
  throw new Error("Invalid path: " + config.home)
2044
2008
  }
2045
2009
 
2046
- // check if the destination already exists => throw error
2047
- let exists = await fse.pathExists(config.home)
2048
- if (exists) {
2049
- throw new Error(`The path ${config.home} already exists. Please remove the folder and retry`)
2050
- }
2010
+ // // check if the destination already exists => throw error
2011
+ // let exists = await fse.pathExists(config.home)
2012
+ // if (exists) {
2013
+ // throw new Error(`The path ${config.home} already exists. Please remove the folder and retry`)
2014
+ // }
2051
2015
 
2052
- this.kernel.store.set("new_home", config.home)
2016
+ //this.kernel.store.set("new_home", config.home)
2017
+ this.kernel.store.set("home", config.home)
2053
2018
  }
2054
2019
 
2055
2020
  }
@@ -2090,24 +2055,28 @@ class Server {
2090
2055
  // if (config.HTTPS_PROXY) {
2091
2056
  // updated.HTTPS_PROXY = config.HTTPS_PROXY
2092
2057
  // }
2093
-
2058
+ if (this.kernel.homedir) {
2059
+ const updated = {
2060
+ HTTP_PROXY: config.HTTP_PROXY,
2061
+ HTTPS_PROXY: config.HTTPS_PROXY,
2062
+ NO_PROXY: config.NO_PROXY,
2063
+ }
2064
+ let fullpath = path.resolve(this.kernel.homedir, "ENVIRONMENT")
2065
+ await Util.update_env(fullpath, updated)
2066
+ }
2094
2067
 
2095
2068
  this.kernel.store.set("HTTP_PROXY", config.HTTP_PROXY)
2096
2069
  this.kernel.store.set("HTTPS_PROXY", config.HTTPS_PROXY)
2097
2070
  this.kernel.store.set("NO_PROXY", config.NO_PROXY)
2098
2071
  }
2099
2072
  async startLogging(homedir) {
2100
- console.log(">>>>>>> startLogging", homedir)
2101
2073
  if (!this.debug) {
2102
2074
  if (this.logInterval) {
2103
2075
  clearInterval(this.logInterval)
2104
2076
  }
2105
2077
  if (homedir) {
2106
- console.log({ homedir })
2107
2078
  let logsdir = path.resolve(homedir, "logs")
2108
- console.log({ logsdir })
2109
2079
  await fs.promises.mkdir(logsdir, { recursive: true }).catch((e) => { console.log(e) })
2110
- console.log("this.log", this.log)
2111
2080
  if (!this.log) {
2112
2081
  this.log = fs.createWriteStream(path.resolve(homedir, "logs/stdout.txt"))
2113
2082
  process.stdout.write = process.stderr.write = this.log.write.bind(this.log)
@@ -2131,14 +2100,35 @@ class Server {
2131
2100
  }
2132
2101
  }
2133
2102
  }
2103
+ async running(port) {
2104
+ let p = port || DEFAULT_PORT
2105
+ const available = await Util.is_port_available(p)
2106
+ if (available) {
2107
+ return false
2108
+ } else {
2109
+ return true
2110
+ }
2111
+ }
2112
+ async getPlugin(name) {
2113
+ let api_path = this.kernel.path("api", name)
2114
+ let pinokio_json_path = this.kernel.path("api", name, "pinokio.json")
2115
+ let pinokio_json = (await this.kernel.loader.load(pinokio_json_path)).resolved
2116
+ let plugin_menu = null
2117
+ if (pinokio_json && pinokio_json.plugin && pinokio_json.plugin.menu && Array.isArray(pinokio_json.plugin.menu)) {
2118
+ let uri = this.kernel.path("api")
2119
+ let config = pinokio_json.plugin
2120
+ await this.renderMenu(uri, name, config, [])
2121
+ return config
2122
+ }
2123
+ return null
2124
+ }
2125
+
2134
2126
  async start(options) {
2135
- console.time("SERVER START")
2136
2127
  this.debug = false
2137
2128
  if (options) {
2138
2129
  this.debug = options.debug
2139
2130
  this.browser = options.browser
2140
2131
  }
2141
- console.time(">>>> 1")
2142
2132
 
2143
2133
  if (this.listening) {
2144
2134
  // stop proxies
@@ -2165,29 +2155,23 @@ class Server {
2165
2155
  // } catch (e) {
2166
2156
  // }
2167
2157
  }
2168
- console.timeEnd(">>>> 1")
2169
2158
 
2170
2159
  // configure from kernel.store
2171
- console.time(">>>> 2")
2172
2160
  await this.syncConfig()
2173
- console.timeEnd(">>>> 2")
2174
2161
 
2175
- console.time(">>>> 3")
2176
2162
  try {
2177
2163
  let _home = this.kernel.store.get("home")
2178
- console.log({ _home })
2179
2164
  if (_home) {
2180
2165
  await this.startLogging(_home)
2181
2166
  }
2182
2167
  } catch (e) {
2183
2168
  console.log("start logging attempt", e)
2184
2169
  }
2185
- console.timeEnd(">>>> 3")
2186
2170
 
2187
2171
  // determine port if port is not passed in
2188
2172
 
2189
2173
  if (!this.port) {
2190
- this.port = 42000
2174
+ this.port = DEFAULT_PORT
2191
2175
  // let platform = os.platform()
2192
2176
  // if (platform === 'linux') {
2193
2177
  // // on linux you are not allowed to listen on ports below 1024
@@ -2211,11 +2195,8 @@ class Server {
2211
2195
  // }
2212
2196
  }
2213
2197
 
2214
- console.log("available port", this.port)
2215
-
2216
2198
  let version = this.kernel.store.get("version")
2217
2199
  let home = this.kernel.store.get("home")
2218
- console.log({ home, version })
2219
2200
 
2220
2201
  let needInitHome = false
2221
2202
  if (home) {
@@ -2232,23 +2213,27 @@ class Server {
2232
2213
  console.log(`[TRY] reset ${p}`)
2233
2214
  await fse.remove(p)
2234
2215
  console.log(`[DONE] reset ${p}`)
2216
+
2217
+ let p2 = path.resolve(home, "prototype")
2218
+ await fse.remove(p2)
2219
+
2235
2220
  console.log("[TRY] Updating to the new version")
2236
2221
  this.kernel.store.set("version", this.version.pinokiod)
2237
2222
  console.log("[DONE] Updating to the new version")
2223
+
2224
+
2238
2225
  }
2239
2226
  }
2240
-
2241
2227
  // initialize kernel
2242
- console.time(">>>> 4 kernel.init")
2228
+
2229
+
2243
2230
  await this.kernel.init({ port: this.port})
2244
- console.timeEnd(">>>> 4 kernel.init")
2231
+ this.kernel.peer.start(this.kernel)
2232
+
2245
2233
 
2246
- console.time(">>>> 5 kernel.initHome")
2247
- console.log({ needInitHome })
2248
2234
  if (needInitHome) {
2249
2235
  await this.kernel.initHome()
2250
2236
  }
2251
- console.timeEnd(">>>> 5 kernel.initHome")
2252
2237
 
2253
2238
  if (this.kernel.homedir) {
2254
2239
  let ex = await this.kernel.exists(this.kernel.homedir, "ENVIRONMENT")
@@ -2350,6 +2335,7 @@ class Server {
2350
2335
 
2351
2336
  if (this.kernel.homedir) {
2352
2337
  this.app.use(express.static(this.kernel.path("web/public")))
2338
+ this.app.use('/prototype', express.static(this.kernel.path("prototype")))
2353
2339
  }
2354
2340
  this.app.use(express.static(path.resolve(__dirname, 'public')));
2355
2341
  this.app.use("/web", express.static(path.resolve(__dirname, "..", "..", "web")))
@@ -2373,6 +2359,15 @@ class Server {
2373
2359
  //let home = this.kernel.homedir
2374
2360
  //let home = this.kernel.store.get("home")
2375
2361
  this.app.get("/", ex(async (req, res) => {
2362
+ // check bin folder
2363
+ // let bin_path = this.kernel.path("bin/miniconda")
2364
+ // let bin_exists = await this.exists(bin_path)
2365
+ // if (!bin_exists) {
2366
+ // res.redirect("/setup")
2367
+ // return
2368
+ // }
2369
+
2370
+
2376
2371
  if (req.query.mode !== "settings" && !home) {
2377
2372
  res.redirect("/?mode=settings")
2378
2373
  return
@@ -2477,15 +2472,30 @@ class Server {
2477
2472
  })
2478
2473
  let meta = {}
2479
2474
  for(let folder of folders) {
2480
- let p = path.resolve(apipath, folder, "pinokio.js")
2481
- let pinokio = (await this.kernel.loader.load(p)).resolved
2482
- if (pinokio) {
2483
- meta[folder] = {
2484
- title: pinokio.title,
2485
- description: pinokio.description,
2486
- icon: pinokio.icon ? `/api/${folder}/${pinokio.icon}?raw=true` : null
2487
- }
2488
- }
2475
+ meta[folder] = await this.kernel.api.meta(folder)
2476
+ // let p = path.resolve(apipath, folder, "pinokio.js")
2477
+ // let pinokio = (await this.kernel.loader.load(p)).resolved
2478
+ // let p2 = path.resolve(apipath, folder, "pinokio_meta.json")
2479
+ // let pinokio2 = (await this.kernel.loader.load(p2)).resolved
2480
+ //
2481
+ // meta[folder] = Object.assign({}, pinokio, pinokio2)
2482
+ // meta[folder].iconpath = meta[folder].icon ? path.resolve(apipath, folder, meta[folder].icon) : null
2483
+ // meta[folder].icon = meta[folder].icon ? `/api/${folder}/${meta[folder].icon}?raw=true` : null
2484
+ // meta[folder].path = path.resolve(apipath, folder)
2485
+
2486
+
2487
+ // if (pinokio) {
2488
+ // meta[folder] = {
2489
+ // title: pinokio.title,
2490
+ // description: pinokio.description,
2491
+ // icon: pinokio.icon ? `/api/${folder}/${pinokio.icon}?raw=true` : null
2492
+ // }
2493
+ // }
2494
+ // if (pinokio2) {
2495
+ // if (pinokio2.title) meta[folder].title = pinokio2.title
2496
+ // if (pinokio2.description) meta[folder].description = pinokio2.description
2497
+ // if (pinokio2.icon) meta[folder].icon = pinokio2.icon
2498
+ // }
2489
2499
  }
2490
2500
  await this.render(req, res, [], meta)
2491
2501
  // if (this.kernel.bin.all_installed) {
@@ -2522,11 +2532,504 @@ class Server {
2522
2532
  // }
2523
2533
  }))
2524
2534
 
2535
+ /*
2536
+ GET /connect => display connection options
2537
+ - github
2538
+ - x
2539
+ */
2540
+ this.app.get("/connect", ex(async (req, res) => {
2541
+ res.render(`connect`, {
2542
+ portal: this.portal,
2543
+ logo: this.logo,
2544
+ theme: this.theme,
2545
+ agent: this.agent,
2546
+ items: [{
2547
+ icon: "fa-brands fa-square-x-twitter",
2548
+ title: "X",
2549
+ description: "Connect with X.com",
2550
+ url: "/connect/x"
2551
+ }, {
2552
+ icon: "fa-brands fa-github",
2553
+ title: "GitHub",
2554
+ description: "Connect with GitHub.com",
2555
+ url: "/github"
2556
+ }]
2557
+ })
2558
+ }))
2559
+ /*
2560
+ * GET /connect/x
2561
+ * GET /connect/discord
2562
+ */
2563
+ this.app.get("/connect/:provider", ex(async (req, res) => {
2564
+
2565
+ // check if all the connect related modules are installed
2566
+ let { requirements, install_required, requirements_pending, error } = await this.kernel.bin.check({
2567
+ bin: this.kernel.bin.preset("connect"),
2568
+ })
2569
+ console.log({ requirements_pending, install_required })
2570
+ if (!requirements_pending && install_required) {
2571
+ res.redirect("/setup/connect?callback=/connect/" + req.params.provider)
2572
+ return
2573
+ }
2574
+
2575
+ // check if router is running
2576
+ let router_running = false
2577
+ let router = this.kernel.router.published()
2578
+ for(let ip in router) {
2579
+ let domains = router[ip]
2580
+ if (domains.includes("pinokio.localhost")) {
2581
+ router_running = true
2582
+ break
2583
+ }
2584
+ }
2585
+ console.log({ router_running })
2586
+ if (!router_running) {
2587
+ res.redirect("/setup/connect?callback=/connect/" + req.params.provider)
2588
+ return
2589
+ }
2590
+
2591
+ // check if caddy is runnign properly
2592
+ // try https://pinokio.localhost
2593
+ // if it works, proceed
2594
+ // if not, redirect
2595
+ let https_running = false
2596
+ try {
2597
+ let res = await axios.get(`http://127.0.0.1:2019/config/`, {
2598
+ timeout: 2000
2599
+ })
2600
+ let test = /pinokio\.localhost/.test(JSON.stringify(res.data))
2601
+ if (test) {
2602
+ https_running = true
2603
+ }
2604
+ } catch (e) {
2605
+ console.log(e)
2606
+ }
2607
+ console.log({ https_running })
2608
+ if (!https_running) {
2609
+ res.redirect("/setup/connect?callback=/connect/" + req.params.provider)
2610
+ return
2611
+ }
2612
+
2613
+ let readme = await this.kernel.connect[req.params.provider].readme()
2614
+ res.render(`connect/${req.params.provider}`, {
2615
+ portal: this.portal,
2616
+ logo: this.logo,
2617
+ theme: this.theme,
2618
+ agent: this.agent,
2619
+ id: this.kernel.connect[req.params.provider].id,
2620
+ readme
2621
+ })
2622
+ }))
2623
+
2624
+ /*
2625
+ * POST /connect/x/login => login and acquire auth token
2626
+ * POST /connect/x/logout => loout
2627
+ * POST /connect/x/keys => return the up-to-date token
2628
+ * POST /connect/x/api => make request
2629
+ *
2630
+ */
2631
+ this.app.post("/connect/:provider/login", ex(async (req, res) => {
2632
+ try {
2633
+ console.log("params", req.params)
2634
+ let response = await this.kernel.connect.login(req.params.provider, req.body)
2635
+ res.json(response)
2636
+ } catch (e) {
2637
+ res.json({ error: e.message })
2638
+ }
2639
+ }))
2640
+ this.app.post("/connect/:provider/logout", ex(async (req, res) => {
2641
+ try {
2642
+ console.log("params", req.params)
2643
+ await this.kernel.connect.logout(req.params.provider, req.body)
2644
+ res.json({ success: true })
2645
+ } catch (e) {
2646
+ res.json({ error: e.message })
2647
+ }
2648
+ }))
2649
+ this.app.post("/connect/:provider/keys", ex(async (req, res) => {
2650
+ try {
2651
+ let response = await this.kernel.connect.keys(req.params.provider)
2652
+ res.json(response)
2653
+ } catch (e) {
2654
+ res.json({ error: e.message })
2655
+ }
2656
+ }))
2657
+ this.app.post("/connect/:provider/api/:method", this.upload.any(), ex(async (req, res) => {
2658
+ try {
2659
+ let response = await this.kernel.connect.request(req.params.provider, req.params.method, req)
2660
+ res.json(response)
2661
+ } catch (e) {
2662
+ console.log("ERROR", e)
2663
+ res.json({ error: e.message })
2664
+ }
2665
+ }))
2525
2666
  this.app.post("/openfs", ex(async (req, res) => {
2526
- Util.openfs(req.body.path, req.body.mode)
2667
+ //Util.openfs(req.body.path, req.body.mode)
2668
+ Util.openfs(req.body.path, req.body, this.kernel)
2527
2669
  res.json({ success: true })
2528
2670
  }))
2529
- this.app.get("/proxy", ex(async (req, res) => {
2671
+ this.app.post("/keys", ex(async (req, res) => {
2672
+ let p = this.kernel.path("key.json")
2673
+ let keys = (await this.kernel.loader.load(p)).resolved
2674
+ console.log("update", req.body)
2675
+ for(let host in req.body) {
2676
+ let updated = req.body[host]
2677
+ for(let indexStr in updated) {
2678
+ let index = parseInt(indexStr)
2679
+ keys[host][index] = updated[indexStr]
2680
+ }
2681
+ }
2682
+ await fs.promises.writeFile(p, JSON.stringify(keys, null, 2))
2683
+ res.json({ success: true })
2684
+ }))
2685
+ this.app.get("/keys", ex(async (req, res) => {
2686
+ let p = this.kernel.path("key.json")
2687
+ let keys = (await this.kernel.loader.load(p)).resolved
2688
+ console.log({ keys })
2689
+ let items = []
2690
+ if (keys) {
2691
+ let sorted_keys = Object.keys(keys)
2692
+ sorted_keys.sort((a, b) => { return a > b })
2693
+ for(let key of sorted_keys) {
2694
+ console.log({ key })
2695
+ items.push({
2696
+ host: key,
2697
+ vals: keys[key]
2698
+ })
2699
+ }
2700
+ }
2701
+ res.render("keys", {
2702
+ filepath: p,
2703
+ theme: this.theme,
2704
+ agent: this.agent,
2705
+ items
2706
+ })
2707
+ }))
2708
+ this.app.get("/docs", ex(async (req, res) => {
2709
+ let url = req.query.url
2710
+ const possiblePaths = [
2711
+ '/openapi.json',
2712
+ '/swagger.json',
2713
+ '/v1/openapi.json',
2714
+ '/v1/swagger.json',
2715
+ '/docs/openapi.json',
2716
+ '/api-docs',
2717
+ '/api-docs.json',
2718
+ ];
2719
+ let selected = null
2720
+ if (req.query.url) {
2721
+ const localHosts = ['localhost', '127.0.0.1', '::1'];
2722
+ const urlObj = new URL(req.query.url)
2723
+ const baseOrigins = [urlObj.origin];
2724
+ if (urlObj.hostname === 'localhost' || urlObj.hostname === '::1' || urlObj.hostname.startsWith('127.')) {
2725
+ for (const host of localHosts) {
2726
+ const origin = urlObj.origin.replace(urlObj.hostname, host);
2727
+ if (!baseOrigins.includes(origin)) {
2728
+ baseOrigins.push(origin);
2729
+ }
2730
+ }
2731
+ }
2732
+
2733
+ for (const origin of baseOrigins) {
2734
+ for (const possiblePath of possiblePaths) {
2735
+ try {
2736
+ const url = new URL(possiblePath, origin).href;
2737
+ const res = await axios.get(url, { timeout: 500 });
2738
+ const contentType = res.headers['content-type'];
2739
+ if (contentType?.includes('application/json')) {
2740
+ const json = res.data;
2741
+ if (json.openapi || json.swagger) {
2742
+ selected = json
2743
+ break
2744
+ }
2745
+ }
2746
+ } catch (e) {
2747
+ console.log("error", e)
2748
+ // ignore errors
2749
+ }
2750
+ }
2751
+ if (selected) break
2752
+ }
2753
+ }
2754
+ let type = "redoc" // "swaggerui"
2755
+ if (req.query.type) {
2756
+ type = req.query.type
2757
+ }
2758
+ if (selected) {
2759
+ res.render(type, {
2760
+ spec: JSON.stringify(selected)
2761
+ })
2762
+ } else {
2763
+ res.render(type, {
2764
+ spec: null
2765
+ })
2766
+ }
2767
+ }))
2768
+ this.app.get("/github", ex(async (req, res) => {
2769
+ let { requirements, install_required, requirements_pending, error } = await this.kernel.bin.check({
2770
+ bin: this.kernel.bin.preset("connect"),
2771
+ })
2772
+ if (!requirements_pending && install_required) {
2773
+ res.redirect("/setup/connect?callback=/github")
2774
+ return
2775
+ }
2776
+ let md = await fs.promises.readFile(path.resolve(__dirname, "..", "kernel/connect/providers/github/README.md"), "utf8")
2777
+ let readme = marked.parse(md)
2778
+ res.render("github", {
2779
+ readme,
2780
+ portal: this.portal,
2781
+ logo: this.logo,
2782
+ theme: this.theme,
2783
+ agent: this.agent,
2784
+ items: [{
2785
+ icon: "fa-solid fa-key",
2786
+ title: "Login",
2787
+ description: "Log into Github",
2788
+ url: "/github/login"
2789
+ }, {
2790
+ icon: "fa-solid fa-check",
2791
+ title: "Status",
2792
+ description: "Check Github login status",
2793
+ url: "/github/status"
2794
+ }, {
2795
+ icon: "fa-solid fa-circle-xmark",
2796
+ title: "Logout",
2797
+ description: "Log out of Github",
2798
+ url: "/github/logout"
2799
+ }]
2800
+ })
2801
+ }))
2802
+ this.app.get("/github/status", ex(async (req, res) => {
2803
+ let id = "gh_status"
2804
+ let params = new URLSearchParams()
2805
+ let message = "gh auth status"
2806
+ params.set("message", encodeURIComponent(message))
2807
+ params.set("path", this.kernel.homedir)
2808
+ // params.set("kill", "/Logged in/i")
2809
+ params.set("kill_message", "Click to return home")
2810
+ params.set("callback", encodeURIComponent("/github"))
2811
+ params.set("id", id)
2812
+ let url = `/shell/${id}?${params.toString()}`
2813
+ res.redirect(url)
2814
+ }))
2815
+ this.app.get("/github/logout", ex(async (req, res) => {
2816
+ let id = "gh_logout"
2817
+ let params = new URLSearchParams()
2818
+ let message = "gh auth logout"
2819
+ params.set("message", encodeURIComponent(message))
2820
+ params.set("path", this.kernel.homedir)
2821
+ // params.set("kill", "/Logged in/i")
2822
+ params.set("kill_message", "Click to return home")
2823
+ params.set("callback", encodeURIComponent("/github"))
2824
+ params.set("id", id)
2825
+ let url = `/shell/${id}?${params.toString()}`
2826
+ res.redirect(url)
2827
+ }))
2828
+ this.app.get("/github/login", ex(async (req, res) => {
2829
+ let id = "gh_login"
2830
+ let params = new URLSearchParams()
2831
+ let delimiter
2832
+ if (this.kernel.platform === "win32") {
2833
+ delimiter = " && "; // must use &&. & doesn't necessariliy wait until the curruent command finishes
2834
+ } else {
2835
+ delimiter = " ; ";
2836
+ }
2837
+ let message = [
2838
+ "gh auth setup-git --hostname github.com --force",
2839
+ "gh auth login --web --git-protocol https"
2840
+ ].join(delimiter)
2841
+ params.set("message", encodeURIComponent(message))
2842
+ params.set("input", true)
2843
+ params.set("path", this.kernel.homedir)
2844
+ params.set("kill", "/Logged in/i")
2845
+ params.set("kill_message", "Your Github account is now connected.")
2846
+ params.set("callback", encodeURIComponent("/github"))
2847
+ params.set("id", id)
2848
+ let url = `/shell/${id}?${params.toString()}`
2849
+ res.redirect(url)
2850
+ }))
2851
+ this.app.get("/shell/:id", ex(async (req, res) => {
2852
+ /*
2853
+ req.query := {
2854
+ path (required), // api, bin, prototype, network, api/
2855
+ message (optional), // if not specified, start an empty shell
2856
+ venv,
2857
+ callback,
2858
+ kill, // regex for killing
2859
+ on.<regex1>: <key>,
2860
+ on.<regex2>: <key>,
2861
+ env.<key1>,
2862
+ env.<key2>,
2863
+ ...
2864
+ }
2865
+ */
2866
+
2867
+ console.log("SHELLS", this.kernel.shell.shells.map((s) => { return { id: s.id, message: s.params.message } }))
2868
+ // create a new term from cwd
2869
+ let id = decodeURIComponent(req.params.id)
2870
+ let cwd = this.kernel.path(this.kernel.api.filePath(decodeURIComponent(req.query.path)))
2871
+ let message = req.query.message ? decodeURIComponent(req.query.message) : null
2872
+ //let message = req.query.message ? req.query.message : null
2873
+ console.log({ message })
2874
+ let venv = req.query.venv ? decodeURIComponent(req.query.venv) : null
2875
+ let input = req.query.input ? true : false
2876
+ let callback = req.query.callback ? decodeURIComponent(req.query.callback) : null
2877
+ let kill_message = req.query.kill_message ? decodeURIComponent(req.query.kill_message) : null
2878
+ let done_message = req.query.done_message ? decodeURIComponent(req.query.done_message) : null
2879
+ let kill = req.query.kill ? decodeURIComponent(req.query.kill) : null
2880
+ let done = req.query.done ? decodeURIComponent(req.query.done) : null
2881
+ let env = {}
2882
+ for(let env_key in req.query) {
2883
+ if (env_key.startsWith("env.")) {
2884
+ let chunks = env_key.split(".")
2885
+ let key = chunks.slice(1).join(".")
2886
+ env[key] = req.query[env_key]
2887
+ }
2888
+ }
2889
+ let conda = {}
2890
+ let conda_exists = false
2891
+ for(let conda_key in req.query) {
2892
+ if (conda_key.startsWith("conda.")) {
2893
+ let chunks = conda_key.split(".")
2894
+ let key = chunks.slice(1).join(".")
2895
+ conda[key] = req.query[conda_key]
2896
+ conda_exists = true
2897
+ }
2898
+ }
2899
+ // let pattern = {}
2900
+ // for(let pattern_key in req.query) {
2901
+ // if (pattern_key.startsWith("pattern.")) {
2902
+ // let chunks = pattern_key.split(".")
2903
+ // let key = chunks.slice(1).join(".")
2904
+ // pattern[key] = req.query[pattern_key]
2905
+ // }
2906
+ // }
2907
+
2908
+ let shell = this.kernel.shell.get(id)
2909
+ console.log({ shell })
2910
+ res.render("shell", {
2911
+ theme: this.theme,
2912
+ agent: this.agent,
2913
+ id,
2914
+ cwd,
2915
+ message,
2916
+ venv,
2917
+ conda: (conda_exists ? conda: null),
2918
+ env,
2919
+ // pattern,
2920
+ input,
2921
+ kill,
2922
+ kill_message,
2923
+ done,
2924
+ done_message,
2925
+ callback,
2926
+ running: (shell ? true : false)
2927
+ })
2928
+ }))
2929
+ // this.app.get("/terminal/:api/:id", ex(async (req, res) => {
2930
+ // res.render("shell", {
2931
+ // theme: this.theme,
2932
+ // agent: this.agent,
2933
+ // cwd: this.kernel.path("api/" + req.params.api),
2934
+ // id: req.params.id
2935
+ // })
2936
+ // }))
2937
+ this.app.get("/peer_check", ex(async (req, res) => {
2938
+ if (this.kernel.peer.refreshing) {
2939
+ res.json({ updated: false })
2940
+ } else {
2941
+ let list = this.getPeerInfo()
2942
+ if (JSON.stringify(this.last_list) !== JSON.stringify(list)) {
2943
+ this.last_list = list
2944
+ res.json({ updated: true })
2945
+ } else {
2946
+ res.json({ updated: false })
2947
+ }
2948
+ }
2949
+ }))
2950
+ this.app.get("/setup", ex(async (req, res) => {
2951
+ let items = []
2952
+ for(let id in Setup) {
2953
+ let item = Setup[id](this.kernel)
2954
+ items.push({
2955
+ id,
2956
+ ...item
2957
+ })
2958
+ }
2959
+ res.render("setup_home", {
2960
+ items,
2961
+ portal: this.portal,
2962
+ logo: this.logo,
2963
+ theme: this.theme,
2964
+ agent: this.agent,
2965
+ })
2966
+ }))
2967
+ this.app.get("/setup/:mode", ex(async (req, res) => {
2968
+ /*
2969
+ 1. mode:ai => all
2970
+ 2. mode:coding => conda, nodejs, git
2971
+ 3. mode:network => conda, git, caddy
2972
+ 4. mode:connect => conda, git, caddy
2973
+ */
2974
+
2975
+ console.log("setup mode", req.params.mode)
2976
+
2977
+ let bin = this.kernel.bin.preset(req.params.mode)
2978
+
2979
+ console.log("__ bin", bin)
2980
+
2981
+ let { requirements, install_required, requirements_pending, error } = await this.kernel.bin.check({
2982
+ bin
2983
+ })
2984
+ // set dependencies for conda
2985
+ for(let i=0; i<requirements.length; i++) {
2986
+ let r = requirements[i]
2987
+ if (r.name === "conda") {
2988
+ requirements[i].dependencies = bin.conda_requirements
2989
+ }
2990
+ }
2991
+ console.log("RE", JSON.stringify(requirements, null, 2))
2992
+ let current = req.query.callback || req.originalUrl
2993
+ res.render("setup", {
2994
+ error,
2995
+ current,
2996
+ install_required,
2997
+ requirements,
2998
+ requirements_pending,
2999
+ logo: this.logo,
3000
+ theme: this.theme,
3001
+ agent: this.agent,
3002
+ })
3003
+ }))
3004
+ this.app.post("/network/reset", ex(async (req, res) => {
3005
+ let caddy_path = this.kernel.path("cache/XDG_DATA_HOME/caddy")
3006
+ await rimraf(caddy_path)
3007
+ res.json({ success: true })
3008
+ }))
3009
+ this.app.get("/network", ex(async (req, res) => {
3010
+ let { requirements, install_required, requirements_pending, error } = await this.kernel.bin.check({
3011
+ bin: this.kernel.bin.preset("network"),
3012
+ })
3013
+ console.log({ requirements, install_required, requirements_pending })
3014
+
3015
+ if (!requirements_pending && install_required) {
3016
+ res.redirect("/setup/network?callback=/network")
3017
+ return
3018
+ }
3019
+
3020
+ let list = this.getPeerInfo()
3021
+
3022
+
3023
+ let peers = []
3024
+ for(let host in this.kernel.peer.info) {
3025
+ let peer_info = this.kernel.peer.info[host]
3026
+ peers.push({
3027
+ host,
3028
+ name: peer_info.name,
3029
+ domain: `https://pinokio.${peer_info.name}.localhost`,
3030
+ router: `https://pinokio.${peer_info.name}.localhost/proxy`
3031
+ })
3032
+ }
2530
3033
  let live_proxies = this.kernel.api.proxies["/proxy"]
2531
3034
  if (!live_proxies) live_proxies = []
2532
3035
  let proxies = []
@@ -2581,18 +3084,40 @@ class Server {
2581
3084
  })
2582
3085
  let apps = []
2583
3086
  for(let folder of folders) {
2584
- let p = path.resolve(apipath, folder, "pinokio.js")
2585
- let pinokio = (await this.kernel.loader.load(p)).resolved
2586
- if (pinokio) {
2587
- apps.push({
2588
- name: pinokio.title,
2589
- description: pinokio.description,
2590
- link: `/pinokio/browser/${folder}/browse#n1`,
2591
- icon: pinokio.icon ? `/api/${folder}/${pinokio.icon}?raw=true` : null
2592
- })
2593
- }
3087
+ let meta = await this.kernel.api.meta(folder)
3088
+ // meta.link = `/pinokio/browser/${folder}/browse#n1`,
3089
+ // meta.icon = meta.icon ? `/api/${folder}/${meta.icon}?raw=true` : null
3090
+ // meta.name = meta.title
3091
+ apps.push(meta)
3092
+ //let p = path.resolve(apipath, folder, "pinokio.js")
3093
+ //let pinokio = (await this.kernel.loader.load(p)).resolved
3094
+ //let p2 = path.resolve(apipath, folder, "pinokio_meta.json")
3095
+ //let pinokio2 = (await this.kernel.loader.load(p2)).resolved
3096
+ //if (pinokio) {
3097
+ // let cc = Object.assign({} , pinokio , pinokio2);
3098
+ // cc.link = `/pinokio/browser/${folder}/browse#n1`,
3099
+ // cc.icon = cc.icon ? `/api/${folder}/${cc.icon}?raw=true` : null
3100
+ // cc.name = cc.title
3101
+ // apps.push(cc)
3102
+ //}
2594
3103
  }
2595
- res.render("proxy", {
3104
+
3105
+
3106
+
3107
+ res.render("network", {
3108
+ docs: this.docs,
3109
+ portal: this.portal,
3110
+ install: this.install,
3111
+ current_host: this.kernel.peer.host,
3112
+ peers,
3113
+ list,
3114
+ name: this.kernel.peer.name,
3115
+ https_active: this.kernel.router.active,
3116
+ peer_active: this.kernel.peer.active,
3117
+ port_mapping: this.kernel.router.port_mapping,
3118
+ // port_mapping: this.kernel.caddy.port_mapping,
3119
+ // ip_mapping: this.kernel.caddy.ip_mapping,
3120
+ lan: this.kernel.router.local_network_mapping,
2596
3121
  agent: this.agent,
2597
3122
  theme: this.theme,
2598
3123
  items: proxies,
@@ -2607,6 +3132,55 @@ class Server {
2607
3132
  let str = await fs.promises.readFile(req.query.logpath, "utf8")
2608
3133
  res.send(str)
2609
3134
  }))
3135
+ this.app.post("/mkdir", ex(async (req, res) => {
3136
+ let folder = req.body.folder
3137
+ let folder_path = path.resolve(this.kernel.api.userdir, req.body.folder)
3138
+ try {
3139
+ // mkdir
3140
+ await fs.promises.mkdir(folder_path)
3141
+
3142
+ // create basic pinokio.json
3143
+
3144
+ // add default icon
3145
+
3146
+ let default_icon_path = path.resolve(__dirname, "public/pinokio-black.png")
3147
+ let icon_path = path.resolve(folder_path, "icon.png")
3148
+ await fs.promises.cp(default_icon_path, icon_path)
3149
+
3150
+
3151
+ // write title/description to pinokio.json
3152
+ let meta_path = path.resolve(folder_path, "pinokio.json")
3153
+ let meta = {
3154
+ title: "No title",
3155
+ description: "",
3156
+ icon: "icon.png"
3157
+ }
3158
+ console.log({ folder_path, default_icon_path, icon_path, meta_path, meta })
3159
+ await fs.promises.writeFile(meta_path, JSON.stringify(meta, null, 2))
3160
+
3161
+ res.json({
3162
+ success: "/pinokio/browser/"+folder
3163
+ })
3164
+ } catch (e) {
3165
+ res.json({
3166
+ error: e.message
3167
+ })
3168
+ }
3169
+ }))
3170
+ this.app.post("/copy", ex(async (req, res) => {
3171
+ let src_path = path.resolve(this.kernel.api.userdir, req.body.src)
3172
+ let dest_path = path.resolve(this.kernel.api.userdir, req.body.dest)
3173
+ try {
3174
+ await fs.promises.cp(src_path, dest_path, { recursive: true })
3175
+ res.json({
3176
+ success: "/pinokio/browser/"+ req.body.dest + "/browse"
3177
+ })
3178
+ } catch (e) {
3179
+ res.json({
3180
+ error: e.message
3181
+ })
3182
+ }
3183
+ }))
2610
3184
  this.app.post("/proxy", ex(async (req, res) => {
2611
3185
  /*
2612
3186
  req.body := {
@@ -2695,10 +3269,133 @@ class Server {
2695
3269
  res.json({ error: "type must be 'local' or 'cloudflare'" })
2696
3270
  }
2697
3271
  }))
3272
+ this.app.get("/prototype/run/*", ex(async (req, res) => {
3273
+ let pathComponents = req.params[0].split("/").concat("pinokio.js")
3274
+ req.base = this.kernel.path("prototype")
3275
+ req.query.callback = "/pinokio/browser/" + req.query.api_path
3276
+ req.query.cwd = this.kernel.path("api", req.query.api_path)
3277
+ await this.render(req, res, pathComponents, null)
3278
+ }))
3279
+ this.app.get("/prototype/show/*", ex(async (req, res) => {
3280
+ let name = req.params[0].split("/").filter((x) => { return x }).join("/")
3281
+ let icon = this.kernel.proto.kv[name].icon
3282
+ let title = this.kernel.proto.kv[name].title
3283
+ let description = this.kernel.proto.kv[name].description
3284
+ let readme = await this.kernel.proto.readme(name)
3285
+ res.render("prototype/show", {
3286
+ icon,
3287
+ title,
3288
+ description,
3289
+ name,
3290
+ prototype_path: name,
3291
+ portal: this.portal,
3292
+ readme,
3293
+ items: this.kernel.proto.items,
3294
+ logo: this.logo,
3295
+ theme: this.theme,
3296
+ agent: this.agent,
3297
+ kernel: this.kernel,
3298
+ })
3299
+ }))
3300
+ this.app.get("/prototype", ex(async (req, res) => {
3301
+ res.render("prototype/index", {
3302
+ portal: this.portal,
3303
+ items: this.kernel.proto.items,
3304
+ logo: this.logo,
3305
+ theme: this.theme,
3306
+ agent: this.agent,
3307
+ kernel: this.kernel,
3308
+ })
3309
+ }))
3310
+ this.app.post("/prototype", this.upload.any(), ex(async (req, res) => {
3311
+ console.log("POST /prototype")
3312
+ try {
3313
+ /*
3314
+ {
3315
+ title,
3316
+ description,
3317
+ path,
3318
+ id
3319
+ }
3320
+ */
3321
+ console.log("req.body", req.body)
3322
+ let formData = req.body
3323
+ for(let key in req.files) {
3324
+ let file = req.files[key]
3325
+ formData[file.fieldname] = file.buffer
3326
+ }
3327
+ console.log({ formData })
3328
+
3329
+
3330
+ // check if the path exists. if it does, return error
3331
+ let api_path = this.kernel.path("api", formData.path)
3332
+ let e = await this.exists(api_path)
3333
+ if (e) {
3334
+ console.log("e", e)
3335
+ console.log("e.message", e.message)
3336
+ res.status(500).json({ error: `The path ${api_path} already exists` })
3337
+ } else {
3338
+ await this.createMeta(formData)
3339
+
3340
+ // run
3341
+
3342
+ res.json({ success: true })
3343
+ }
3344
+ } catch (e) {
3345
+ console.log("e", e)
3346
+ console.log("e.message", e.message)
3347
+ res.status(500).json({ error: e.message })
3348
+ }
3349
+ }))
3350
+ this.app.post("/new", this.upload.any(), ex(async (req, res) => {
3351
+ try {
3352
+ /*
3353
+ {
3354
+ title,
3355
+ description,
3356
+ path,
3357
+ id
3358
+ }
3359
+ */
3360
+ console.log("req.body", req.body)
3361
+ let formData = req.body
3362
+ for(let key in req.files) {
3363
+ let file = req.files[key]
3364
+ formData[file.fieldname] = file.buffer
3365
+ }
3366
+ console.log({ formData })
3367
+
3368
+
3369
+ // check if the path exists. if it does, return error
3370
+ let api_path = this.kernel.path("api", formData.path)
3371
+ let e = await this.exists(api_path)
3372
+ if (e) {
3373
+ console.log("e", e)
3374
+ console.log("e.message", e.message)
3375
+ res.status(500).json({ error: `The path ${api_path} already exists` })
3376
+ } else {
3377
+ await this.createMeta(formData)
3378
+ res.json({ success: true })
3379
+ }
3380
+ } catch (e) {
3381
+ console.log("e", e)
3382
+ console.log("e.message", e.message)
3383
+ res.status(500).json({ error: e.message })
3384
+ }
3385
+ }))
2698
3386
  this.app.post("/env", ex(async (req, res) => {
2699
3387
  let fullpath = path.resolve(this.kernel.homedir, req.body.filepath, "ENVIRONMENT")
2700
3388
  let updated = req.body.vals
3389
+ let hosts = req.body.hosts
2701
3390
  await Util.update_env(fullpath, updated)
3391
+ // for all environment variables that have hosts, save the key as well
3392
+ // hosts := { env_key: host }
3393
+ for(let env in hosts) {
3394
+ let host = hosts[env]
3395
+ let val = updated[env]
3396
+ console.log({ hosts, updated, host, val })
3397
+ await this.kernel.kv.set(host.value, val, host.index)
3398
+ }
2702
3399
  res.json({})
2703
3400
  }))
2704
3401
  this.app.get("/env", ex(async (req, res) => {
@@ -2744,6 +3441,8 @@ class Server {
2744
3441
  }
2745
3442
  if (config.icon) {
2746
3443
  config.icon = `/${env_path}/${config.icon}?raw=true`
3444
+ } else {
3445
+ config.icon = "/pinokio-black.png"
2747
3446
  }
2748
3447
 
2749
3448
  let name
@@ -2804,6 +3503,8 @@ class Server {
2804
3503
  config.pre.forEach((item) => {
2805
3504
  if (item.icon) {
2806
3505
  item.icon = `/api/${req.params.name}/${item.icon}?raw=true`
3506
+ } else {
3507
+ item.icon = "/pinokio-black.png"
2807
3508
  }
2808
3509
  if (!item.href.startsWith("http")) {
2809
3510
  item.href = path.resolve(this.kernel.homedir, "api", req.params.name, item.href)
@@ -2885,7 +3586,6 @@ class Server {
2885
3586
  }
2886
3587
  }
2887
3588
  }
2888
- console.log({ cloudflare_links, local_links })
2889
3589
  res.render("share_editor", {
2890
3590
  cloudflare_links,
2891
3591
  local_links,
@@ -2914,8 +3614,13 @@ class Server {
2914
3614
  }))
2915
3615
  this.app.get("/du/:name", ex(async (req, res) => {
2916
3616
  let p = this.kernel.path("api", req.params.name)
2917
- let d1 = await Util.du(p)
2918
- res.json({ du: d1 })
3617
+ try {
3618
+ let d1 = await Util.du(p)
3619
+ res.json({ du: d1 })
3620
+ } catch (e) {
3621
+ console.log("disk usage error", e)
3622
+ res.json({ du: 0 })
3623
+ }
2919
3624
  }))
2920
3625
  this.app.get("/edit/*", ex(async (req, res) => {
2921
3626
  let pathComponents = req.params[0].split("/")
@@ -2959,7 +3664,13 @@ class Server {
2959
3664
  }))
2960
3665
  this.app.get("/api/*", ex(async (req, res) => {
2961
3666
  let pathComponents = req.params[0].split("/")
2962
- if (req.query && 'fs' in req.query) {
3667
+ if (req.query && 'command' in req.query) {
3668
+ let full_filepath = this.kernel.path("api", ...pathComponents)
3669
+ Util.openfs(full_filepath, { command: req.query.command })
3670
+ res.render("fs", {
3671
+ path: full_filepath
3672
+ })
3673
+ } else if (req.query && 'fs' in req.query) {
2963
3674
  // open in file system
2964
3675
  let full_filepath = this.kernel.path("api", ...pathComponents)
2965
3676
  if (req.query.fs) {
@@ -2985,6 +3696,20 @@ class Server {
2985
3696
  }
2986
3697
  }
2987
3698
  }))
3699
+ this.app.get("/pinokio/dynamic/:name", ex(async (req, res) => {
3700
+ let plugin = await this.getPlugin(req.params.name)
3701
+ let plugin_menu = null
3702
+ let html = ""
3703
+ if (plugin && plugin.menu && Array.isArray(plugin.menu)) {
3704
+ plugin_menu = plugin.menu
3705
+ html = await new Promise((resolve, reject) => {
3706
+ ejs.renderFile(path.resolve(__dirname, "views/partials/dynamic.ejs"), { dynamic: plugin.menu }, (err, html) => {
3707
+ resolve(html)
3708
+ })
3709
+ })
3710
+ }
3711
+ res.send(html)
3712
+ }))
2988
3713
  this.app.get("/pinokio/sidebar/:name", ex(async (req, res) => {
2989
3714
  let name = req.params.name
2990
3715
  let app_path = this.kernel.path("api", name, "pinokio.js")
@@ -3007,7 +3732,10 @@ class Server {
3007
3732
  let files = await fs.promises.readdir(p, { withFileTypes: true })
3008
3733
  files = files.filter((file) => {
3009
3734
  return file.name.endsWith(".json") || file.name.endsWith(".js")
3735
+ }).filter((file) => {
3736
+ return file.name !== "pinokio.js" && file.name !== "pinokio.json" && file.name !== "pinokio_meta.json"
3010
3737
  })
3738
+ console.log("files", files)
3011
3739
  config = {
3012
3740
  title: name,
3013
3741
  menu: files.map((file) => {
@@ -3039,6 +3767,27 @@ class Server {
3039
3767
  */
3040
3768
 
3041
3769
  }))
3770
+ this.app.post("/pinokio/peer/refresh", ex(async (req, res) => {
3771
+ // refresh and broadcast
3772
+ console.log("/peer/refresh")
3773
+ await this.kernel.refresh()
3774
+ res.json({ success: true })
3775
+ }))
3776
+ this.app.get("/pinokio/peer", ex(async (req, res) => {
3777
+ // await this.kernel.refresh()
3778
+ res.json({
3779
+ home: this.kernel.homedir,
3780
+ arch: this.kernel.arch,
3781
+ platform: this.kernel.platform,
3782
+ name: this.kernel.peer.name,
3783
+ host: this.kernel.peer.host,
3784
+ port_mapping: this.kernel.router.port_mapping,
3785
+ //router: this.kernel.router.info(),
3786
+ proc: this.kernel.processes.info,
3787
+ router: this.kernel.router.published(),
3788
+ memory: this.kernel.memory
3789
+ })
3790
+ }))
3042
3791
  this.app.get("/pinokio/memory", ex((req, res) => {
3043
3792
  let filepath = req.query.filepath
3044
3793
  let mem = this.getMemory(filepath)
@@ -3282,6 +4031,88 @@ class Server {
3282
4031
  }
3283
4032
  res.redirect(webpath)
3284
4033
  }))
4034
+ this.app.post("/pinokio/upload", this.upload.any(), ex(async (req, res) => {
4035
+ console.log("POST /pinokio/upload")
4036
+ try {
4037
+
4038
+
4039
+ /*
4040
+ 1. edit
4041
+ 2. copy
4042
+ 3. copy + edit
4043
+ 4. move
4044
+ 5. move + edit
4045
+
4046
+ */
4047
+
4048
+ let formData = req.body
4049
+ for(let key in req.files) {
4050
+ let file = req.files[key]
4051
+ formData[file.fieldname] = file.buffer
4052
+ }
4053
+ console.log({ formData })
4054
+
4055
+ if (formData.edit) {
4056
+ if (formData.copy) {
4057
+ if (formData.old_path !== formData.new_path) {
4058
+
4059
+ // 1. copy first
4060
+ let old_path = this.kernel.path("api", formData.old_path)
4061
+ let new_path = this.kernel.path("api", formData.new_path)
4062
+ console.log({ old_path, new_path })
4063
+ await fs.promises.cp(old_path, new_path, { recursive: true })
4064
+
4065
+ // 2. edit meta in the new_path
4066
+ await this.updateMeta(formData, formData.new_path)
4067
+
4068
+ }
4069
+ } else if (formData.move) {
4070
+
4071
+ // 1. move first
4072
+ if (formData.old_path !== formData.new_path) {
4073
+ let old_path = this.kernel.path("api", formData.old_path)
4074
+ let new_path = this.kernel.path("api", formData.new_path)
4075
+ console.log({ old_path, new_path })
4076
+ await fs.promises.rename(old_path, new_path)
4077
+ }
4078
+
4079
+ // 2. edit meta in the new_path
4080
+ await this.updateMeta(formData, formData.new_path)
4081
+ } else {
4082
+ // 1. edit only
4083
+ if (formData.old_path === formData.new_path) {
4084
+ await this.updateMeta(formData, formData.new_path)
4085
+ }
4086
+ }
4087
+ } else {
4088
+ if (formData.copy) {
4089
+ // 1. copy only
4090
+ let old_path = this.kernel.path("api", formData.old_path)
4091
+ let new_path = this.kernel.path("api", formData.new_path)
4092
+ console.log({ old_path, new_path })
4093
+ await fs.promises.cp(old_path, new_path, { recursive: true })
4094
+ } else if (formData.move) {
4095
+ // 2. move only
4096
+ let old_path = this.kernel.path("api", formData.old_path)
4097
+ let new_path = this.kernel.path("api", formData.new_path)
4098
+ console.log({ old_path, new_path })
4099
+ await fs.promises.rename(old_path, new_path)
4100
+ } else {
4101
+ // nothing
4102
+ }
4103
+ }
4104
+ res.json({
4105
+ success: true,
4106
+ reload: formData.new_path,
4107
+ new_path: formData.new_path,
4108
+ })
4109
+ } catch (e) {
4110
+ console.log("e", e)
4111
+ console.log("e.message", e.message)
4112
+ res.status(500).json({ error: e.message })
4113
+ }
4114
+
4115
+ }))
3285
4116
  /*
3286
4117
  SYNTAX
3287
4118
  fs.uri(<bin|api>, path)
@@ -3341,12 +4172,15 @@ class Server {
3341
4172
  object that's not (array, file, blob, uint8array, arraybuffer) => Object
3342
4173
  the rest => typeof(value)
3343
4174
  */
4175
+ console.log("req", req)
3344
4176
  let formData = req.body
3345
4177
  for(let key in req.files) {
3346
4178
  let file = req.files[key]
3347
4179
  formData[file.fieldname] = file.buffer
3348
4180
  }
3349
4181
 
4182
+ console.log({ formData })
4183
+
3350
4184
  const drive = formData.drive
3351
4185
  const home = formData.path
3352
4186
  const method = formData.method
@@ -3475,11 +4309,42 @@ class Server {
3475
4309
 
3476
4310
  }))
3477
4311
  this.app.get("/pinokio/requirements_ready", ex((req, res) => {
3478
-
3479
4312
  let requirements_pending = !this.kernel.bin.installed_initialized
3480
4313
  console.log({ requirements_pending })
3481
4314
  res.json({ requirements_pending })
3482
4315
  }))
4316
+ this.app.get("/check_peer", ex((req, res) => {
4317
+ if (this.kernel.peer.active) {
4318
+ // if network is active, return success only if the router is up for all of its peers (including itself)
4319
+ console.log({ peer_info: this.kernel.peer.info })
4320
+ console.time("check_peer")
4321
+ let ready = true
4322
+ if (this.kernel.peer.info && Object.keys(this.kernel.peer.info).length > 0) {
4323
+ for(let host in this.kernel.peer.info) {
4324
+ let info = this.kernel.peer.info[host]
4325
+ if (info.router && Object.keys(info.router).length > 0) {
4326
+ ready = true
4327
+ } else {
4328
+ ready = false
4329
+ break;
4330
+ }
4331
+ }
4332
+ } else {
4333
+ ready = false;
4334
+ }
4335
+ console.log({ info: this.kernel.peer.info, ready })
4336
+ console.timeEnd("check_peer")
4337
+ if (ready) {
4338
+ res.json({ success: true })
4339
+ } else {
4340
+ res.json({ success: false })
4341
+ }
4342
+ } else {
4343
+ // if network is not active, return success immediately (just checking if the server is up)
4344
+ console.log("this.kernel.router.published()")
4345
+ res.json({ success: true })
4346
+ }
4347
+ }))
3483
4348
  this.app.get("/check", ex((req, res) => {
3484
4349
  res.json({ success: true })
3485
4350
  }))
@@ -3487,6 +4352,17 @@ class Server {
3487
4352
  console.log("post /restart")
3488
4353
  this.start({ debug: this.debug, browser: this.browser })
3489
4354
  }))
4355
+ this.app.post("/network", ex(async (req, res) => {
4356
+ if (this.kernel.homedir) {
4357
+ let fullpath = path.resolve(this.kernel.homedir, "ENVIRONMENT")
4358
+ console.log("req.body", req.body)
4359
+ await Util.update_env(fullpath, req.body)
4360
+ res.json({ success: true })
4361
+ } else {
4362
+ res.json({ error: "homedir doesn't exist" })
4363
+ }
4364
+ }))
4365
+
3490
4366
  this.app.post("/config", ex(async (req, res) => {
3491
4367
  try {
3492
4368
  await this.setConfig(req.body)
@@ -3508,15 +4384,58 @@ class Server {
3508
4384
  stack: err.stack
3509
4385
  })
3510
4386
  });
3511
- // process.on('SIGINT', () => {
3512
- // console.log("SIGINT Event")
3513
- // if (this.kernel && this.kernel.shell) this.kernel.shell.reset()
3514
- // process.exit()
3515
- // })
3516
- // process.on('SIGTERM', () => {
3517
- // console.log("SIGTERM Event")
3518
- // if (this.kernel && this.kernel.shell) this.kernel.shell.reset()
3519
- // process.exit()
4387
+ process.on('SIGINT', () => {
4388
+ //if (this.kernel && this.kernel.shell) {
4389
+ // console.log("shell reset")
4390
+ // this.kernel.shell.reset(() => {
4391
+ // process.exit()
4392
+ // })
4393
+ //} else {
4394
+ // process.exit()
4395
+ //}
4396
+ console.log("[SigInt event] Kill", process.pid)
4397
+ if (this.kernel.processes.caddy_pid) {
4398
+ console.log("kill caddy", this.kernel.processes.caddy_pid)
4399
+ kill(this.kernel.processes.caddy_pid, "SIGKILL", true)
4400
+ }
4401
+ console.log("kill self")
4402
+ kill(process.pid, 'SIGKILL', true)
4403
+ //kill(process.pid, map, 'SIGKILL', () => {
4404
+ // console.log("child procs killed for", process.pid)
4405
+ // process.exit()
4406
+ //});
4407
+ })
4408
+
4409
+ process.on('SIGTERM', () => {
4410
+ // if (this.kernel && this.kernel.shell) {
4411
+ // console.log("shell reset")
4412
+ // this.kernel.shell.reset(() => {
4413
+ // process.exit()
4414
+ // })
4415
+ // } else {
4416
+ // process.exit()
4417
+ // }
4418
+ console.log("[Sigterm event] Kill", process.pid)
4419
+ if (this.kernel.processes.caddy_pid) {
4420
+ console.log("kill caddy", this.kernel.processes.caddy_pid)
4421
+ kill(this.kernel.processes.caddy_pid, "SIGKILL", true)
4422
+ }
4423
+ console.log("kill self")
4424
+ kill(process.pid, 'SIGKILL', true)
4425
+ //let map = this.kernel.processes.map || {}
4426
+ //kill(process.pid, map, 'SIGKILL', () => {
4427
+ // console.log("child procs killed for", process.pid)
4428
+ // process.exit()
4429
+ //});
4430
+ })
4431
+ // process.on('exit', () => {
4432
+ // console.log("[Exit event]")
4433
+ // kill(process.pid, 'SIGKILL', true)
4434
+ // //let map = this.kernel.processes.map || {}
4435
+ // //kill(process.pid, map, 'SIGKILL', () => {
4436
+ // // console.log("child procs killed for", process.pid)
4437
+ // // process.exit()
4438
+ // //});
3520
4439
  // })
3521
4440
  // process.on('exit', () => {
3522
4441
  // console.log("exit Event")
@@ -3540,7 +4459,8 @@ class Server {
3540
4459
  server: this.listening
3541
4460
  });
3542
4461
  })
3543
- console.timeEnd("SERVER START")
4462
+ // this.kernel.peer.start(this.kernel)
4463
+
3544
4464
 
3545
4465
  }
3546
4466
  }