pinokiod 8.2.2 → 8.2.4

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/kernel/index.js CHANGED
@@ -35,6 +35,7 @@ const Plugin = require('./plugin')
35
35
  const Router = require("./router")
36
36
  const PinokioDomainRouter = require("./router/pinokio_domain_router")
37
37
  const Procs = require('./procs')
38
+ const Anywhere = require('./anywhere')
38
39
  const Peer = require('./peer')
39
40
  const Git = require('./git')
40
41
  const Vault = require('./vault')
@@ -711,6 +712,15 @@ class Kernel {
711
712
 
712
713
  }
713
714
  }
715
+ // 8b. Pinokio Anywhere: refresh the tailscale serve config from the apps'
716
+ // published URLs. No-op unless Anywhere is on and its daemon is running.
717
+ if (this.anywhere) {
718
+ try {
719
+ await this.anywhere.sync()
720
+ } catch (err) {
721
+ console.warn('[anywhere] sync failed:', err && err.message ? err.message : err)
722
+ }
723
+ }
714
724
  // 6. tell peers to refresh
715
725
  let changed
716
726
  let new_config = JSON.stringify(await this.peer.current_host())
@@ -1103,12 +1113,17 @@ class Kernel {
1103
1113
  if (this.peer) {
1104
1114
  this.peer.stop()
1105
1115
  }
1116
+ if (this.anywhere) {
1117
+ // re-initializing the kernel (home change): drop the old daemon
1118
+ await this.anywhere.stop().catch(() => {})
1119
+ }
1106
1120
 
1107
1121
  this.info = new Info(this)
1108
1122
  this.pipe = new Pipe(this)
1109
1123
  this.proto = new Proto(this)
1110
1124
  this.plugin = new Plugin(this)
1111
1125
  this.processes = new Procs(this)
1126
+ this.anywhere = new Anywhere(this)
1112
1127
  this.kv = new KV(this)
1113
1128
  this.cloudflare = new Cloudflare()
1114
1129
  this.peer = new Peer(this)
package/kernel/shell.js CHANGED
@@ -21,7 +21,7 @@ const ShellParser = require('./shell_parser')
21
21
  const AnsiStreamTracker = require('./ansi_stream_tracker')
22
22
  const ShellStateSync = require('./shell_state_sync')
23
23
  const ShellRunTemplate = require('./api/shell_run_template')
24
- const { PYTHON_INSTALL_SPEC } = require('./bin/conda-pins')
24
+ const { PYTHON_INSTALL_SPEC, PIP_INSTALL_COMMAND, PIP_MIN_VERSION } = require('./bin/conda-pins')
25
25
  const CondaRuntimeGuard = require('./shell_conda_runtime_guard')
26
26
  const home = os.homedir()
27
27
  const WINDOWS_CMD_CONDA_COMMAND_PATTERN = /(?:^|[&|()]\s*)@?\s*(?:call\s+)?(?:"[^"]*[\\/]conda(?:\.bat|\.exe)?"|(?:[a-z]:)?[^&|()\s"]*[\\/]conda(?:\.bat|\.exe)?|conda(?:\.bat|\.exe)?)\s+/i
@@ -1399,10 +1399,17 @@ class Shell {
1399
1399
  deactivate_path,
1400
1400
  // timeout,
1401
1401
  activate_command,
1402
+ this.isPowerShell()
1403
+ ? `${PIP_INSTALL_COMMAND}; if ($LASTEXITCODE -ne 0) { exit 1 }`
1404
+ : `${PIP_INSTALL_COMMAND} || exit 1`,
1402
1405
  // timeout,
1403
1406
  ]
1404
1407
  }
1405
1408
  }
1409
+ if (!use_uv) {
1410
+ const checkPip = `python -I -c "from importlib.metadata import distributions; assert all(tuple(map(int, d.version.split('.')[:3])) >= (${PIP_MIN_VERSION.split(".").join(", ")}) for d in distributions(name='pip')), 'Update pip in this app environment before running it.'"`
1411
+ venv_activation.push(this.isPowerShell() ? `${checkPip}; if ($LASTEXITCODE -ne 0) { exit 1 }` : `${checkPip} || exit 1`)
1412
+ }
1406
1413
  } else {
1407
1414
  venv_activation = []
1408
1415
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "8.2.2",
3
+ "version": "8.2.4",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/server/index.js CHANGED
@@ -497,6 +497,13 @@ class Server {
497
497
  if (this.kernel && this.kernel.processes && this.kernel.processes.caddy_pid) {
498
498
  this.killProcessTree(this.kernel.processes.caddy_pid, 'kill caddy')
499
499
  }
500
+ if (this.kernel && this.kernel.anywhere) {
501
+ try {
502
+ this.kernel.anywhere.killSync()
503
+ } catch (error) {
504
+ console.error('Failed to kill tailscaled', error)
505
+ }
506
+ }
500
507
  this.killProcessTree(process.pid, 'kill self')
501
508
  }
502
509
  exists (s) {
@@ -1011,6 +1018,64 @@ class Server {
1011
1018
  routes.sort((a, b) => String(a.name || "").localeCompare(String(b.name || "")))
1012
1019
  return routes
1013
1020
  }
1021
+ // ---------------- Pinokio Anywhere ----------------
1022
+ portFromUrl(value) {
1023
+ if (!value || typeof value !== "string") return null
1024
+ try {
1025
+ const parsed = new URL(value)
1026
+ if (parsed.port) return Number(parsed.port)
1027
+ if (parsed.protocol === "https:") return 443
1028
+ if (parsed.protocol === "http:") return 80
1029
+ } catch (_) { }
1030
+ return null
1031
+ }
1032
+ async composeAnywhereStatus({ light } = {}) {
1033
+ const anywhere = this.kernel ? this.kernel.anywhere : null
1034
+ if (!anywhere) {
1035
+ return { phase: "setup", installed: false, enabled: false, running: false }
1036
+ }
1037
+ let snap
1038
+ try {
1039
+ snap = await anywhere.snapshot()
1040
+ } catch (error) {
1041
+ return { phase: "error", installed: true, enabled: true, running: false, error: error && error.message ? error.message : String(error) }
1042
+ }
1043
+ if (light) {
1044
+ return snap
1045
+ }
1046
+ snap.apps = []
1047
+ if (snap.phase === "ready") {
1048
+ const serveByLocalPort = new Map((snap.serve || []).map((entry) => [entry.local, entry]))
1049
+ let running = []
1050
+ try {
1051
+ running = await this.collectHomeServerApps()
1052
+ } catch (_) {
1053
+ running = []
1054
+ }
1055
+ for (const app of running) {
1056
+ const port = this.portFromUrl(app.ready_url || app.url)
1057
+ const entry = port ? serveByLocalPort.get(port) : null
1058
+ snap.apps.push({
1059
+ name: app.name,
1060
+ icon: app.icon,
1061
+ state: app.state,
1062
+ remote: entry ? entry.url : null,
1063
+ exposed: !!entry,
1064
+ })
1065
+ }
1066
+ }
1067
+ if (snap.connectUrl) {
1068
+ if (!this.anywhereQrCache || this.anywhereQrCache.url !== snap.connectUrl) {
1069
+ try {
1070
+ this.anywhereQrCache = { url: snap.connectUrl, data: await QRCode.toDataURL(snap.connectUrl, { margin: 1, scale: 6 }) }
1071
+ } catch (_) {
1072
+ this.anywhereQrCache = { url: snap.connectUrl, data: null }
1073
+ }
1074
+ }
1075
+ snap.qr = this.anywhereQrCache.data
1076
+ }
1077
+ return snap
1078
+ }
1014
1079
  async collectHomeServerApps() {
1015
1080
  const apps = []
1016
1081
  if (!this.kernel || !this.kernel.api || typeof this.kernel.api.listApps !== "function") {
@@ -4900,6 +4965,12 @@ class Server {
4900
4965
  // config.icon = "/pinokio-white.png"
4901
4966
  // }
4902
4967
  // }
4968
+ // Pinokio Anywhere: a visitor on the node's own Tailscale name gets each
4969
+ // published app's fixed https address; any other host is left for the
4970
+ // Home Server rewrite below, exactly as before.
4971
+ if (this.kernel.anywhere) {
4972
+ await this.kernel.anywhere.rewriteMenu(req.$source && req.$source.host, config.menu)
4973
+ }
4903
4974
  config = Util.rewrite_localhost(this.kernel, config, req.$source)
4904
4975
  return config
4905
4976
  } else {
@@ -6757,6 +6828,8 @@ class Server {
6757
6828
  protocol,
6758
6829
  host: forwardedHost || req.get("host")
6759
6830
  }
6831
+ // Pinokio Anywhere: only a visitor on the node's own Tailscale name gets the web-app manifest
6832
+ res.locals.anywhere_host = !!(this.kernel && this.kernel.anywhere && this.kernel.anywhere.isNodeHost(req.$source.host))
6760
6833
  next()
6761
6834
  })
6762
6835
  if (this.kernel.homedir) {
@@ -14159,6 +14232,56 @@ class Server {
14159
14232
  allow_dns_creation,
14160
14233
  })
14161
14234
  }))
14235
+ this.app.get("/anywhere", ex(async (req, res) => {
14236
+ let { requirements, install_required, requirements_pending, error } = await this.kernel.bin.check({
14237
+ bin: this.kernel.bin.preset("anywhere"),
14238
+ })
14239
+ if (!requirements_pending && install_required) {
14240
+ res.redirect("/setup/anywhere?callback=/anywhere")
14241
+ return
14242
+ }
14243
+ const anywhere = this.kernel.anywhere
14244
+ // first visit after install: switch it on, the page then walks the user
14245
+ // through sign-in. Later visits respect the on/off switch.
14246
+ if (anywhere && !requirements_pending && !(await anywhere.configured())) {
14247
+ anywhere.enable().catch((err) => {
14248
+ console.log("[anywhere] enable failed", err && err.message ? err.message : err)
14249
+ })
14250
+ }
14251
+ const status = await this.composeAnywhereStatus()
14252
+ res.render("anywhere", {
14253
+ status,
14254
+ requirements_pending,
14255
+ install_required,
14256
+ error,
14257
+ docs: this.docs,
14258
+ portal: this.portal,
14259
+ logo: this.logo,
14260
+ theme: this.theme,
14261
+ agent: req.agent,
14262
+ })
14263
+ }))
14264
+ this.app.get("/anywhere/status", ex(async (req, res) => {
14265
+ res.set("Cache-Control", "no-store")
14266
+ res.json(await this.composeAnywhereStatus({ light: req.query.light === "1" }))
14267
+ }))
14268
+ const anywhereAction = (label, fn) => ex(async (req, res) => {
14269
+ const anywhere = this.kernel.anywhere
14270
+ if (!anywhere) {
14271
+ res.status(500).json({ success: false, error: "Pinokio Anywhere is not available" })
14272
+ return
14273
+ }
14274
+ try {
14275
+ await fn(anywhere, req)
14276
+ res.json({ success: true, status: await this.composeAnywhereStatus() })
14277
+ } catch (error) {
14278
+ console.log(`[anywhere] ${label} failed`, error)
14279
+ res.status(500).json({ success: false, error: error && error.message ? error.message : String(error) })
14280
+ }
14281
+ })
14282
+ this.app.post("/anywhere/start", anywhereAction("start", (anywhere) => anywhere.enable()))
14283
+ this.app.post("/anywhere/stop", anywhereAction("stop", (anywhere) => anywhere.disable()))
14284
+ this.app.post("/anywhere/logout", anywhereAction("logout", (anywhere) => anywhere.logout()))
14162
14285
  this.app.get("/network", ex(async (req, res) => {
14163
14286
  let protocol = req.get('X-Forwarded-Proto') || "http"
14164
14287
  let { requirements, install_required, requirements_pending, error } = await this.kernel.bin.check({
@@ -0,0 +1 @@
1
+ {"name":"Pinokio","short_name":"Pinokio","start_url":"/","scope":"/","display":"standalone","background_color":"#ffffff","theme_color":"#000000","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png","purpose":"any maskable"},{"src":"/apple-touch-icon.png","sizes":"180x180","type":"image/png"}]}