pinokiod 8.0.3 → 8.0.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.
@@ -8,6 +8,7 @@ const Util = require('../util')
8
8
  const ADMIN_READY_TIMEOUT_MS = 30000
9
9
  const ADMIN_START_TIMEOUT_MS = 5000
10
10
  const ADMIN_POLL_INTERVAL_MS = 250
11
+ const ADMIN_STOP_TIMEOUT_MS = 5000
11
12
 
12
13
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
13
14
 
@@ -29,6 +30,35 @@ class Caddy {
29
30
  return false
30
31
  }
31
32
  }
33
+ async waitForStopped(timeout = ADMIN_STOP_TIMEOUT_MS, interval = ADMIN_POLL_INTERVAL_MS) {
34
+ const started = Date.now()
35
+ while ((Date.now() - started) < timeout) {
36
+ if (!(await this.running())) {
37
+ return true
38
+ }
39
+ await sleep(interval)
40
+ }
41
+ return !(await this.running())
42
+ }
43
+ async stop() {
44
+ if (!(await this.running())) {
45
+ return false
46
+ }
47
+ try {
48
+ await axios.post('http://127.0.0.1:2019/stop', null, {
49
+ timeout: ADMIN_STOP_TIMEOUT_MS
50
+ })
51
+ } catch (error) {
52
+ if (await this.running()) {
53
+ throw error
54
+ }
55
+ }
56
+ const stopped = await this.waitForStopped()
57
+ if (!stopped) {
58
+ throw new Error("Pinokio could not stop Caddy before replacing the managed Conda runtime.")
59
+ }
60
+ return true
61
+ }
32
62
  async start() {
33
63
  // if peer.https_active is true,
34
64
  // 1. kill existing caddy
@@ -170,6 +170,16 @@ report_errors: false`)
170
170
  ondata({ raw: `downloading installer: ${installer_url}...\r\n` })
171
171
  await this.kernel.bin.download(installer_url, installer, ondata)
172
172
 
173
+ const caddy = this.kernel.bin.mod && this.kernel.bin.mod.caddy
174
+ let caddyWasRunning = false
175
+ if (caddy && typeof caddy.running === "function") {
176
+ caddyWasRunning = await caddy.running()
177
+ }
178
+ if (caddyWasRunning) {
179
+ ondata({ raw: `stopping Home Server before replacing Miniforge...\r\n` })
180
+ await caddy.stop()
181
+ }
182
+
173
183
  legacy_path_exists = await this.kernel.exists(`bin/${LEGACY_CONDA_ROOT_DIR}`)
174
184
  if (legacy_path_exists) {
175
185
  console.log("Removing legacy install path...", legacy_path)
@@ -202,8 +212,12 @@ report_errors: false`)
202
212
  await fs.promises.writeFile(this.kernel.path(`bin/${CONDA_ROOT_DIR}/conda-meta/pinned`), this.pinnedPackages())
203
213
  }
204
214
 
215
+ const dependencies = new Set(Array.isArray(req.dependencies) ? req.dependencies : [])
216
+ if (caddyWasRunning) {
217
+ dependencies.add("caddy")
218
+ }
205
219
  let mods = this.kernel.bin.mods.filter((m) => {
206
- return req.dependencies.includes(m.name)
220
+ return dependencies.has(m.name)
207
221
  }).map((m) => {
208
222
  if (m.mod.cmd) {
209
223
  return m.mod.cmd()
@@ -241,6 +255,10 @@ report_errors: false`)
241
255
  await this.ensureCompatibilityAlias()
242
256
  ondata({ raw: `Install finished\r\n` })
243
257
  await this.kernel.bin.rm(installer, ondata)
258
+ if (caddyWasRunning) {
259
+ ondata({ raw: `restarting Home Server after replacing Miniforge...\r\n` })
260
+ await caddy.start()
261
+ }
244
262
  }
245
263
  async removeInstallPath(target) {
246
264
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "8.0.3",
3
+ "version": "8.0.4",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -51,3 +51,68 @@ test('caddy admin wait returns true when admin responds', async () => {
51
51
  assert.equal(ready, true)
52
52
  assert.equal(checks, 2)
53
53
  })
54
+
55
+ test('caddy start uses the normal shell execution contract', async () => {
56
+ const caddy = new Caddy()
57
+ let running = false
58
+ let launch
59
+ caddy.kernel = {
60
+ homedir: '/tmp/pinokio',
61
+ peer: {
62
+ https_active: true,
63
+ announce() {}
64
+ },
65
+ processes: {},
66
+ exec(params, ondata) {
67
+ launch = params
68
+ running = true
69
+ ondata({ raw: '', cleaned: 'admin endpoint started' })
70
+ }
71
+ }
72
+ caddy.running = async () => running
73
+ caddy.installed = async () => true
74
+
75
+ await caddy.start()
76
+
77
+ assert.equal(launch.message, 'caddy run --watch')
78
+ assert.deepEqual(Object.keys(launch).sort(), ['message', 'path'])
79
+ })
80
+
81
+ test('caddy stop uses the native admin API only when Caddy is running', async () => {
82
+ const caddy = new Caddy()
83
+ let running = true
84
+ caddy.running = async () => running
85
+ const originalPost = require('axios').post
86
+ const calls = []
87
+ require('axios').post = async (...args) => {
88
+ calls.push(args)
89
+ running = false
90
+ }
91
+
92
+ try {
93
+ assert.equal(await caddy.stop(), true)
94
+ assert.equal(await caddy.stop(), false)
95
+ } finally {
96
+ require('axios').post = originalPost
97
+ }
98
+
99
+ assert.equal(calls.length, 1)
100
+ assert.equal(calls[0][0], 'http://127.0.0.1:2019/stop')
101
+ })
102
+
103
+ test('caddy stop refuses to continue when native shutdown is not confirmed', async () => {
104
+ const caddy = new Caddy()
105
+ caddy.running = async () => true
106
+ caddy.waitForStopped = async () => false
107
+ const originalPost = require('axios').post
108
+ require('axios').post = async () => {}
109
+
110
+ try {
111
+ await assert.rejects(
112
+ caddy.stop(),
113
+ /could not stop Caddy/
114
+ )
115
+ } finally {
116
+ require('axios').post = originalPost
117
+ }
118
+ })
@@ -262,6 +262,60 @@ test('Conda install replaces the stale runtime and rebuilds declared Conda modul
262
262
  )
263
263
  })
264
264
 
265
+ test('Conda replacement stops, preserves, and restarts a running managed Caddy', async () => {
266
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-caddy-lifecycle-'))
267
+ await createMiniforge(root)
268
+ const kernel = createKernel(root, 'win32')
269
+ const events = []
270
+ let caddyRunning = true
271
+ let condaInstall
272
+
273
+ const caddy = {
274
+ cmd: () => 'caddy=2.9.1',
275
+ running: async () => caddyRunning,
276
+ stop: async () => {
277
+ events.push('caddy-stop')
278
+ caddyRunning = false
279
+ },
280
+ start: async () => {
281
+ events.push('caddy-start')
282
+ caddyRunning = true
283
+ },
284
+ }
285
+ kernel.bin.installed.conda = new Set(['caddy'])
286
+ kernel.bin.mod = { caddy }
287
+ kernel.bin.mods = [{ name: 'caddy', mod: caddy }]
288
+ kernel.bin.download = async () => events.push('download')
289
+ kernel.bin.rm = async () => events.push('installer-cleanup')
290
+ kernel.bin.exec = async (payload) => {
291
+ if (payload && payload.conda && payload.conda.skip) {
292
+ events.push('bootstrap')
293
+ const miniforge = path.join(root, 'bin', 'miniforge')
294
+ await fs.mkdir(path.join(miniforge, 'conda-meta'), { recursive: true })
295
+ await fs.writeFile(path.join(miniforge, 'python.exe'), 'fake python\n')
296
+ return
297
+ }
298
+ events.push('conda-install')
299
+ condaInstall = payload
300
+ }
301
+
302
+ const conda = createConda(kernel)
303
+ const removeInstallPath = conda.removeInstallPath.bind(conda)
304
+ conda.removeInstallPath = async (target) => {
305
+ assert.equal(caddyRunning, false)
306
+ events.push('remove-miniforge')
307
+ await removeInstallPath(target)
308
+ }
309
+
310
+ await conda._install({ dependencies: [] }, () => {})
311
+
312
+ assert.ok(events.indexOf('caddy-stop') < events.indexOf('remove-miniforge'))
313
+ assert.ok(events.indexOf('remove-miniforge') < events.indexOf('bootstrap'))
314
+ assert.ok(events.indexOf('conda-install') < events.indexOf('caddy-start'))
315
+ assert.match(condaInstall.message[1], / caddy=2\.9\.1$/)
316
+ assert.equal(caddyRunning, true)
317
+ })
318
+
265
319
  test('Conda install replaces legacy miniconda with a compatibility alias to miniforge', async () => {
266
320
  const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-miniforge-alias-'))
267
321
  const legacy = await createLegacyMiniconda(root)