pinokiod 7.2.10 → 7.2.12

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.
@@ -5,136 +5,184 @@ const fs = require('fs')
5
5
  const Util = require('./util')
6
6
  const TEMP_ENV_KEYS = ["TMP", "TEMP", "TMPDIR", "PIP_TMPDIR"]
7
7
  const CACHE_ENV_KEYS = ["UV_CACHE_DIR", "PIP_CACHE_DIR"]
8
+ const CACHE_PREFLIGHT_KEYS = TEMP_ENV_KEYS.concat(CACHE_ENV_KEYS)
8
9
 
9
- const envKey = (env, key) => {
10
- return Object.keys(env).find((candidate) => candidate.toLowerCase() === key.toLowerCase())
11
- }
12
-
13
- const setEnv = (env, key, value) => {
14
- for (const candidate of Object.keys(env)) {
15
- if (candidate.toLowerCase() === key.toLowerCase() && candidate !== key) {
16
- delete env[candidate]
17
- }
10
+ const formatCachePreflightError = (error) => {
11
+ if (!error) {
12
+ return ""
18
13
  }
19
- env[key] = value
20
- }
21
-
22
- const normalizeEnvPath = (value, root) => {
23
- if (typeof value !== "string") {
24
- return null
14
+ const parts = []
15
+ if (error.code) {
16
+ parts.push(`code=${error.code}`)
17
+ }
18
+ if (typeof error.errno !== "undefined") {
19
+ parts.push(`errno=${error.errno}`)
25
20
  }
26
- const trimmed = value.trim()
27
- if (!trimmed) {
28
- return null
21
+ if (error.syscall) {
22
+ parts.push(`syscall=${error.syscall}`)
29
23
  }
30
- if (path.isAbsolute(trimmed)) {
31
- return path.resolve(trimmed)
24
+ if (error.path) {
25
+ parts.push(`path=${error.path}`)
32
26
  }
33
- if (trimmed.startsWith("./") || trimmed.startsWith(".\\")) {
34
- return path.resolve(root, trimmed)
27
+ if (error.dest) {
28
+ parts.push(`dest=${error.dest}`)
35
29
  }
36
- return path.resolve(root, trimmed)
30
+ if (error.message) {
31
+ parts.push(`message=${error.message}`)
32
+ }
33
+ return parts.join(" ")
37
34
  }
38
35
 
39
- const isInsidePath = (candidate, root) => {
40
- const rel = path.relative(path.resolve(root), path.resolve(candidate))
41
- return rel === "" || (!!rel && !rel.startsWith("..") && !path.isAbsolute(rel))
36
+ const logCachePreflight = (message) => {
37
+ console.log(`[Pinokio cache preflight] ${message}`)
42
38
  }
43
39
 
44
- const canWriteDirectory = async (dirPath) => {
45
- if (!dirPath) {
46
- return false
47
- }
48
- const testDir = path.resolve(
40
+ const probeCacheDir = async (dirPath) => {
41
+ const probeDir = path.resolve(
49
42
  dirPath,
50
- `.pinokio-write-test-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`
43
+ `.pinokio-cache-probe-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`
51
44
  )
52
- const testFile = path.resolve(testDir, "probe.tmp")
53
- const renamedFile = path.resolve(testDir, "probe-renamed.tmp")
45
+ const probeFile = path.resolve(probeDir, "probe.tmp")
46
+ const renamedFile = path.resolve(probeDir, "probe-renamed.tmp")
47
+ const steps = [
48
+ ["create probe directory", () => fs.promises.mkdir(probeDir, { recursive: false })],
49
+ ["write probe file", () => fs.promises.writeFile(probeFile, "pinokio")],
50
+ ["append probe file", () => fs.promises.appendFile(probeFile, "-cache-probe")],
51
+ ["rename probe file", () => fs.promises.rename(probeFile, renamedFile)],
52
+ ["delete probe file", () => fs.promises.unlink(renamedFile)],
53
+ ["remove probe directory", () => fs.promises.rmdir(probeDir)]
54
+ ]
55
+
56
+ for (const [step, run] of steps) {
57
+ try {
58
+ await run()
59
+ } catch (error) {
60
+ await fs.promises.rm(probeDir, { recursive: true, force: true }).catch(() => {})
61
+ return { ok: false, step, error }
62
+ }
63
+ }
64
+ return { ok: true }
65
+ }
66
+
67
+ const managedCacheEnvDefaults = () => {
68
+ const defaults = {}
69
+ for (const key of CACHE_PREFLIGHT_KEYS) {
70
+ defaults[key] = `./cache/${key}`
71
+ }
72
+ return defaults
73
+ }
74
+
75
+ const ensureCachePreflightDir = async (key, targetPath, options = {}) => {
76
+ logCachePreflight(`${key}: target=${targetPath}`)
54
77
  try {
55
- await fs.promises.mkdir(testDir, { recursive: true })
56
- await fs.promises.writeFile(testFile, "pinokio")
57
- await fs.promises.appendFile(testFile, "-probe")
58
- await fs.promises.rename(testFile, renamedFile)
59
- await fs.promises.unlink(renamedFile)
60
- await fs.promises.rmdir(testDir)
61
- return true
78
+ await fs.promises.mkdir(targetPath, { recursive: true })
79
+ logCachePreflight(`${key}: mkdir ok`)
62
80
  } catch (error) {
63
- await fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => {})
64
- return false
81
+ logCachePreflight(`${key}: mkdir failed ${formatCachePreflightError(error)}`)
65
82
  }
66
- }
67
83
 
68
- const ensureWritableDirectory = async (dirPath, options = {}) => {
69
- if (!dirPath) {
70
- return false
84
+ const firstProbe = await probeCacheDir(targetPath)
85
+ if (firstProbe.ok) {
86
+ logCachePreflight(`${key}: probe ok`)
87
+ return { key, path: targetPath, repaired: false, ok: true }
71
88
  }
72
- const repair = !!options.repair
89
+
90
+ logCachePreflight(`${key}: probe failed step="${firstProbe.step}" ${formatCachePreflightError(firstProbe.error)}`)
91
+ logCachePreflight(`${key}: repair delete start path=${targetPath}`)
92
+
73
93
  try {
74
- await fs.promises.mkdir(dirPath, { recursive: true })
94
+ await fs.promises.rm(targetPath, { recursive: true, force: true })
95
+ logCachePreflight(`${key}: repair delete ok`)
75
96
  } catch (error) {
76
- if (!repair) {
77
- return false
97
+ logCachePreflight(`${key}: repair delete failed ${formatCachePreflightError(error)}`)
98
+ if (typeof options.elevatedRepair !== "function") {
99
+ return { key, path: targetPath, repaired: false, ok: false, step: "repair delete", error }
78
100
  }
79
- await fs.promises.rm(dirPath, { recursive: true, force: true }).catch(() => {})
101
+ logCachePreflight(`${key}: elevated repair start path=${targetPath}`)
102
+ let elevatedRepair
80
103
  try {
81
- await fs.promises.mkdir(dirPath, { recursive: true })
82
- } catch (mkdirError) {
83
- return false
104
+ elevatedRepair = await options.elevatedRepair(targetPath, logCachePreflight)
105
+ } catch (repairError) {
106
+ elevatedRepair = { ok: false, error: repairError }
84
107
  }
108
+ if (!elevatedRepair || !elevatedRepair.ok) {
109
+ return { key, path: targetPath, repaired: false, elevated: true, ok: false, step: "elevated repair", error: elevatedRepair && elevatedRepair.error ? elevatedRepair.error : error }
110
+ }
111
+ const elevatedProbe = await probeCacheDir(targetPath)
112
+ if (elevatedProbe.ok) {
113
+ logCachePreflight(`${key}: elevated repair probe ok`)
114
+ return { key, path: targetPath, repaired: true, elevated: true, ok: true }
115
+ }
116
+ logCachePreflight(`${key}: elevated repair probe failed step="${elevatedProbe.step}" ${formatCachePreflightError(elevatedProbe.error)}`)
117
+ return { key, path: targetPath, repaired: true, elevated: true, ok: false, step: elevatedProbe.step, error: elevatedProbe.error }
85
118
  }
86
- if (await canWriteDirectory(dirPath)) {
87
- return true
88
- }
89
- if (!repair) {
90
- return false
91
- }
92
- await fs.promises.rm(dirPath, { recursive: true, force: true }).catch(() => {})
119
+
93
120
  try {
94
- await fs.promises.mkdir(dirPath, { recursive: true })
121
+ await fs.promises.mkdir(targetPath, { recursive: true })
122
+ logCachePreflight(`${key}: repair mkdir ok`)
95
123
  } catch (error) {
96
- return false
124
+ logCachePreflight(`${key}: repair mkdir failed ${formatCachePreflightError(error)}`)
125
+ return { key, path: targetPath, repaired: true, ok: false, step: "repair mkdir", error }
97
126
  }
98
- return canWriteDirectory(dirPath)
99
- }
100
127
 
101
- const canRepairManagedCachePath = (candidate, cacheRoot) => {
102
- const rel = path.relative(path.resolve(cacheRoot), path.resolve(candidate))
103
- return !!rel && !rel.startsWith("..") && !path.isAbsolute(rel)
104
- }
105
-
106
- const managedCacheEnvDefaults = () => {
107
- const defaults = {}
108
- for (const key of TEMP_ENV_KEYS.concat(CACHE_ENV_KEYS)) {
109
- defaults[key] = `./cache/${key}`
128
+ const secondProbe = await probeCacheDir(targetPath)
129
+ if (secondProbe.ok) {
130
+ logCachePreflight(`${key}: repair probe ok`)
131
+ return { key, path: targetPath, repaired: true, ok: true }
110
132
  }
111
- return defaults
133
+
134
+ logCachePreflight(`${key}: repair probe failed step="${secondProbe.step}" ${formatCachePreflightError(secondProbe.error)}`)
135
+ return { key, path: targetPath, repaired: true, ok: false, step: secondProbe.step, error: secondProbe.error }
112
136
  }
113
137
 
114
- const ensurePinokioCacheDirs = async (kernel) => {
138
+ const ensurePinokioCacheDirs = async (kernel, options = {}) => {
115
139
  if (!kernel || !kernel.homedir) {
116
140
  return {}
117
141
  }
142
+ const throwOnFailure = !!options.throwOnFailure
118
143
  const root = path.resolve(kernel.homedir)
119
144
  const cacheRoot = path.resolve(root, "cache")
120
145
  const envPath = path.resolve(root, "ENVIRONMENT")
121
146
  const defaults = managedCacheEnvDefaults()
147
+ logCachePreflight(`start root=${root}`)
122
148
  await Util.update_env(envPath, defaults)
123
- const env = await get(root, kernel)
149
+ logCachePreflight(`ENVIRONMENT updated keys=${CACHE_PREFLIGHT_KEYS.join(",")}`)
150
+ try {
151
+ await fs.promises.mkdir(cacheRoot, { recursive: true })
152
+ logCachePreflight(`cache root mkdir ok path=${cacheRoot}`)
153
+ } catch (error) {
154
+ logCachePreflight(`cache root mkdir failed path=${cacheRoot} ${formatCachePreflightError(error)}`)
155
+ }
156
+
157
+ const errors = []
158
+ const results = []
124
159
 
125
- for (const key of TEMP_ENV_KEYS.concat(CACHE_ENV_KEYS)) {
126
- const existingKey = envKey(env, key)
127
- const dirPath = normalizeEnvPath(existingKey ? env[existingKey] : defaults[key], root)
128
- const managedPath = path.resolve(cacheRoot, key)
129
- const targetPath = dirPath && isInsidePath(dirPath, root) ? dirPath : managedPath
130
- const repair = canRepairManagedCachePath(targetPath, cacheRoot)
131
- if (!repair || !(await ensureWritableDirectory(targetPath, { repair: true }))) {
132
- throw new Error(`Pinokio could not create a writable ${key} directory: ${targetPath}`)
160
+ for (const key of CACHE_PREFLIGHT_KEYS) {
161
+ const targetPath = path.resolve(cacheRoot, key)
162
+ const result = await ensureCachePreflightDir(key, targetPath, options)
163
+ results.push(result)
164
+ if (!result.ok) {
165
+ errors.push(result)
133
166
  }
134
- setEnv(env, key, targetPath)
135
167
  }
136
168
 
137
- return env
169
+ if (errors.length > 0) {
170
+ kernel.cacheDirErrors = errors
171
+ const message = errors
172
+ .map((error) => `${error.key}: ${error.path} (${error.step || "unknown"} ${formatCachePreflightError(error.error)})`)
173
+ .join(", ")
174
+ logCachePreflight(`failed ${message}`)
175
+ if (throwOnFailure) {
176
+ throw new Error(`Pinokio could not create writable cache directories: ${message}`)
177
+ }
178
+ } else {
179
+ kernel.cacheDirErrors = []
180
+ logCachePreflight(`complete ok checked=${results.length} repaired=${results.filter((result) => result.repaired).length}`)
181
+ }
182
+
183
+ kernel.cacheDirPreflight = results
184
+ const env = await get(root, kernel)
185
+ return { env, errors, results }
138
186
  }
139
187
  const ENVS = async () => {
140
188
  // const primary_port = 80
package/kernel/index.js CHANGED
@@ -44,6 +44,7 @@ const WatchManager = require('./watch')
44
44
  const { DownloaderHelper } = require('node-downloader-helper');
45
45
  const { ProxyAgent } = require('proxy-agent');
46
46
  const fakeUa = require('fake-useragent');
47
+ const sudo = process.platform === "win32" ? require("sudo-prompt-programfiles-x86") : null
47
48
  //const kill = require('./tree-kill');
48
49
  const kill = require('kill-sync')
49
50
  const ejs = require('ejs');
@@ -55,6 +56,34 @@ const VARS = {
55
56
  }
56
57
  }
57
58
 
59
+ const powershellSingleQuote = (value) => {
60
+ return `'${String(value).replace(/'/g, "''")}'`
61
+ }
62
+
63
+ const windowsCacheRepairCommand = (targetPath) => {
64
+ const psPath = powershellSingleQuote(targetPath)
65
+ return [
66
+ "powershell.exe",
67
+ "-NoProfile",
68
+ "-ExecutionPolicy Bypass",
69
+ "-Command",
70
+ `"`,
71
+ "$ErrorActionPreference = 'Stop';",
72
+ `$p = ${psPath};`,
73
+ "$account = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name;",
74
+ "$grant = $account + ':(OI)(CI)F';",
75
+ "if (Test-Path -LiteralPath $p) {",
76
+ " & takeown.exe /F $p /R /D Y;",
77
+ " & icacls.exe $p /reset /T /C;",
78
+ " & icacls.exe $p /grant $grant /T /C;",
79
+ " Remove-Item -LiteralPath $p -Recurse -Force;",
80
+ "}",
81
+ "New-Item -ItemType Directory -Force -Path $p | Out-Null;",
82
+ "& icacls.exe $p /grant $grant /T /C;",
83
+ `"`
84
+ ].join(" ")
85
+ }
86
+
58
87
  //const memwatch = require('@airbnb/node-memwatch');
59
88
  class Kernel {
60
89
  schema = "<=7.0.0"
@@ -465,6 +494,30 @@ class Kernel {
465
494
  return ''
466
495
  }
467
496
  }
497
+ async elevatedCacheRepair(targetPath, log = console.log) {
498
+ if (this.platform !== "win32" || !sudo) {
499
+ return { ok: false, error: new Error("Elevated cache repair is only available on Windows") }
500
+ }
501
+ const command = windowsCacheRepairCommand(targetPath)
502
+ log(`elevated repair command=${command}`)
503
+ return new Promise((resolve) => {
504
+ sudo.exec(command, { name: "Pinokio" }, (error, stdout, stderr) => {
505
+ if (stdout && stdout.trim()) {
506
+ log(`elevated repair stdout ${stdout.trim()}`)
507
+ }
508
+ if (stderr && stderr.trim()) {
509
+ log(`elevated repair stderr ${stderr.trim()}`)
510
+ }
511
+ if (error) {
512
+ log(`elevated repair failed ${error.message || error}`)
513
+ resolve({ ok: false, error, stdout, stderr })
514
+ } else {
515
+ log(`elevated repair ok path=${targetPath}`)
516
+ resolve({ ok: true, stdout, stderr })
517
+ }
518
+ })
519
+ })
520
+ }
468
521
  async ensureRouterMode() {
469
522
  const domain = await this.resolvePinokioDomain()
470
523
  const shouldUseCustom = domain.length > 0
@@ -1053,7 +1106,10 @@ class Kernel {
1053
1106
 
1054
1107
  // 2. mkdir all the folders if not already created
1055
1108
  await Environment.init_folders(this.homedir, this)
1056
- await Environment.ensurePinokioCacheDirs(this)
1109
+ await Environment.ensurePinokioCacheDirs(this, {
1110
+ throwOnFailure: true,
1111
+ elevatedRepair: this.elevatedCacheRepair.bind(this)
1112
+ })
1057
1113
 
1058
1114
  // if key.json doesn't exist, create an empty json file
1059
1115
  let ee = await this.exists(this.homedir, "key.json")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "7.2.10",
3
+ "version": "7.2.12",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/server/index.js CHANGED
@@ -5707,6 +5707,16 @@ class Server {
5707
5707
 
5708
5708
  needsManagedRefresh = true
5709
5709
  console.log("[TRY] Updating to the new version")
5710
+ let envPath = path.resolve(home, "ENVIRONMENT")
5711
+ let envExists = await this.kernel.exists(envPath)
5712
+ if (!envExists) {
5713
+ let str = await Environment.ENV("system", home, this.kernel)
5714
+ await fs.promises.writeFile(envPath, str)
5715
+ }
5716
+ await Environment.ensurePinokioCacheDirs(this.kernel, {
5717
+ throwOnFailure: true,
5718
+ elevatedRepair: this.kernel.elevatedCacheRepair.bind(this.kernel)
5719
+ })
5710
5720
  this.kernel.store.set("version", this.version.pinokiod)
5711
5721
  console.log("[DONE] Updating to the new version")
5712
5722
  console.log("not up to date. update py.")