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/Dockerfile +3 -2
- package/kernel/anywhere/index.js +706 -0
- package/kernel/anywhere/localapi.js +147 -0
- package/kernel/anywhere/ports.js +51 -0
- package/kernel/anywhere/serve.js +95 -0
- package/kernel/bin/bun.js +1 -1
- package/kernel/bin/conda-meta.js +15 -0
- package/kernel/bin/conda-pins.js +22 -0
- package/kernel/bin/conda.js +66 -5
- package/kernel/bin/index.js +4 -3
- package/kernel/bin/node.js +1 -1
- package/kernel/bin/setup.js +29 -0
- package/kernel/bin/tailscaled.js +72 -0
- package/kernel/bin/uv.js +1 -1
- package/kernel/index.js +15 -0
- package/kernel/shell.js +8 -1
- package/package.json +1 -1
- package/server/index.js +123 -0
- package/server/public/anywhere.webmanifest +1 -0
- package/server/views/anywhere.ejs +436 -0
- package/server/views/app.ejs +3 -1
- package/server/views/index.ejs +6 -0
- package/server/views/partials/main_sidebar.ejs +39 -0
- package/test/anywhere.test.js +461 -0
- package/test/conda-bin.test.js +126 -8
- package/test/node-bin.test.js +3 -3
- package/test/shell-run-template.test.js +46 -0
- package/test/uv-bin.test.js +5 -5
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
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"}]}
|