@centralu/host-linux-arm64 0.1.0-beta.46 → 0.1.0-beta.48

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.
@@ -1,10 +1,9 @@
1
1
  // Centralu's remote installer, step 2 (docs/plans/remote-hub.md §10.2). The hub sends this file over
2
2
  // ssh and runs it on the pinned Node that step 1 placed; it never runs on the hub. It imports Node's
3
3
  // own modules only, and reads one argument: the base64 of a JSON object the hub wrote
4
- // ({ version, node, platform, hub, packages: [{ name, tarball, integrity }], activate? }), or for the
5
- // update's later steps ({ action: 'pointers', current, previous } and { action: 'prune' }; plan §10.5), or a dev
6
- // build of the host from a git commit in a working copy there ({ action: 'dev', repo, commit, … }; `pnpm remote-dev`, #1036)
7
- // and the copy of the store taken before it first runs ({ action: 'store-backup' | 'store-restore' }).
4
+ // ({ version, node, platform, hub, packages: [{ name, tarball, integrity }], activate? }), or a dev build of the host
5
+ // from a git commit in a working copy there ({ action: 'dev', repo, commit, … }; `pnpm remote-dev`, #1036). What an
6
+ // update does after placing (stop, switch, start, check, put back, prune) is the new version's own `serve --switch`.
8
7
  //
9
8
  // It prints one line for the hub to read, `CENTRALU-INSTALL done <json>` or
10
9
  // `CENTRALU-INSTALL fail <code> <detail>`, and nothing that matters on its exit code (exit codes do
@@ -38,7 +37,6 @@ export function remoteLayout(dataDir) {
38
37
  node: join(root, 'node'),
39
38
  bin: join(root, 'bin'),
40
39
  lock: join(root, 'install.lock'),
41
- storeBackup: join(root, 'store-backup'),
42
40
  }
43
41
  }
44
42
 
@@ -72,53 +70,6 @@ export function switchTo(l, next) {
72
70
  return { current: next, previous: cur ?? readPointer(l.previous) }
73
71
  }
74
72
 
75
- /**
76
- * Removes every version that is neither `current` nor `previous`, and every Node neither uses. A
77
- * folder that cannot go (a Windows program still running from it) stays and is named, never fatal
78
- */
79
- export function prune(l) {
80
- const keep = [readPointer(l.current), readPointer(l.previous)].filter(Boolean)
81
- const removed = []
82
- const left = []
83
- const sweep = (dir, kept) => {
84
- let names = []
85
- try {
86
- names = readdirSync(dir)
87
- } catch {
88
- return
89
- }
90
- for (const name of names) {
91
- if (kept.has(name) || name.startsWith('.')) continue
92
- try {
93
- rmSync(join(dir, name), { recursive: true, force: true })
94
- removed.push(name)
95
- } catch {
96
- left.push(name)
97
- }
98
- }
99
- }
100
- sweep(l.versions, new Set(keep.map((p) => p.version)))
101
- // What `placeVersion` moved aside and could not remove then
102
- try {
103
- for (const name of readdirSync(l.root).filter((n) => n.startsWith('.replaced-'))) {
104
- try {
105
- rmSync(join(l.root, name), { recursive: true, force: true })
106
- } catch {
107
- left.push(name)
108
- }
109
- }
110
- } catch {}
111
- sweep(l.node, new Set(keep.map((p) => `v${p.node}`)))
112
- // The store copies of versions that are gone (#1036): nothing can start them to need the copy any more. Not reported:
113
- // `removed` names versions and Nodes
114
- try {
115
- for (const name of readdirSync(l.storeBackup).filter((n) => !keep.some((p) => p.version === n))) rmSync(join(l.storeBackup, name), { recursive: true, force: true })
116
- } catch {
117
- // none, or one still open; the next prune tries again
118
- }
119
- return { removed, left }
120
- }
121
-
122
73
  /** A lock folder with no pid in it, younger than this, is held: its installer has made it and not yet written the pid */
123
74
  const LOCK_YOUNG_MS = 60_000
124
75
 
@@ -313,8 +264,8 @@ export async function install(p, { dataDir = process.env.CC_DATA_DIR || join(hom
313
264
  return { installed: { version: p.version, node: p.node }, current: readPointer(l.current), previous: readPointer(l.previous), removed: [], left: [] }
314
265
  }
315
266
  const launcher = await ensureManagedLauncher(l, dir, p.version, platform)
316
- const pointers = switchTo(l, { version: p.version, node: p.node })
317
- return { ...pointers, ...prune(l), launcher }
267
+ // Nothing is pruned here: the switch that replaces a version prunes (the launcher's serve --switch, plan §10.5a)
268
+ return { ...switchTo(l, { version: p.version, node: p.node }), removed: [], left: [], launcher }
318
269
  } finally {
319
270
  unlock()
320
271
  }
@@ -337,121 +288,6 @@ async function ensureManagedLauncher(l, dir, version, platform) {
337
288
  return launcher
338
289
  }
339
290
 
340
- const pointerOf = (v) => (v && WORD.test(v.version) && WORD.test(v.node) ? { version: v.version, node: v.node } : null)
341
-
342
- /**
343
- * Writes both pointers as given (plan §10.5 steps 3 and 5, and rollback): `previous` first, then
344
- * `current`, each by rename; a null `previous` removes that file. `current` must name a version and a
345
- * Node that are both there. A null `current` puts the machine back to having no managed version: the
346
- * pointers and the launcher go, so the lookup finds the Centralu it ran before (an npm install).
347
- * Nothing is removed from `versions/` or `node/`
348
- */
349
- export async function setPointers(p, { dataDir = process.env.CC_DATA_DIR || join(homedir(), '.centralu'), platform = process.platform } = {}) {
350
- const l = remoteLayout(dataDir)
351
- mkdirSync(l.root, { recursive: true })
352
- const unlock = lock(l)
353
- try {
354
- const current = pointerOf(p.current)
355
- const previous = pointerOf(p.previous)
356
- if (!current) {
357
- for (const f of [l.previous, l.current, launcherPath(l, platform)]) rmSync(f, { force: true })
358
- return { current: null, previous: null }
359
- }
360
- const dir = join(l.versions, current.version)
361
- // Both must be there: a `previous` naming nothing would offer a rollback to a version that is gone
362
- for (const v of [current, previous].filter(Boolean)) {
363
- if (!existsSync(join(l.versions, v.version, 'install.json')) || !existsSync(join(l.node, `v${v.node}`))) throw new Refusal('missing', `${v.version} ${v.node}`)
364
- }
365
- await ensureManagedLauncher(l, dir, current.version, platform)
366
- if (previous) writeAtomic(l.previous, `${previous.version} ${previous.node}\n`)
367
- else rmSync(l.previous, { force: true })
368
- writeAtomic(l.current, `${current.version} ${current.node}\n`)
369
- return { current, previous }
370
- } finally {
371
- unlock()
372
- }
373
- }
374
-
375
- /** Plan §10.5 step 6, on its own: what is neither `current` nor `previous` goes */
376
- export function pruneNow({ dataDir = process.env.CC_DATA_DIR || join(homedir(), '.centralu') } = {}) {
377
- const l = remoteLayout(dataDir)
378
- mkdirSync(l.root, { recursive: true })
379
- const unlock = lock(l)
380
- try {
381
- return { current: readPointer(l.current), previous: readPointer(l.previous), ...prune(l) }
382
- } finally {
383
- unlock()
384
- }
385
- }
386
-
387
- /** The files of the host's store in the data folder: the database and SQLite's write-ahead log beside it */
388
- const STORE_FILES = ['store.db', 'store.db-wal', 'store.db-shm']
389
-
390
- /**
391
- * A copy of the store taken while no host runs, before a dev build first opens it (#1036): a dev build may migrate the
392
- * store past what the version before it reads (a step that raises `min_reader_version`), and that version would then
393
- * refuse to start (`StoreTooNewError`), leaving the machine with no host. The copy is kept per `owner`, the version
394
- * that left the store so (`store-backup/<owner>/`), replaced whole, and `before` names the build it was taken before.
395
- * A copy goes with its version (`prune`)
396
- */
397
- export function storeBackup(p, { dataDir = process.env.CC_DATA_DIR || join(homedir(), '.centralu') } = {}) {
398
- if (!WORD.test(String(p.owner)) || !WORD.test(String(p.before))) throw new Refusal('params', 'store backup')
399
- const l = remoteLayout(dataDir)
400
- mkdirSync(l.storeBackup, { recursive: true })
401
- const unlock = lock(l)
402
- try {
403
- const partial = join(l.storeBackup, `.partial-${process.pid}`)
404
- rmSync(partial, { recursive: true, force: true })
405
- try {
406
- mkdirSync(partial, { recursive: true })
407
- const files = STORE_FILES.filter((f) => existsSync(join(dataDir, f)))
408
- for (const f of files) cpSync(join(dataDir, f), join(partial, f))
409
- writeFileSync(join(partial, 'backup.json'), JSON.stringify({ owner: p.owner, before: p.before, files, at: new Date().toISOString() }, null, 2) + '\n')
410
- rmSync(join(l.storeBackup, p.owner), { recursive: true, force: true })
411
- renameSync(partial, join(l.storeBackup, p.owner))
412
- return { saved: files }
413
- } catch (err) {
414
- throw err instanceof Refusal ? err : new Refusal('store_backup', err.message)
415
- } finally {
416
- rmSync(partial, { recursive: true, force: true })
417
- }
418
- } finally {
419
- unlock()
420
- }
421
- }
422
-
423
- /**
424
- * Puts back the copy `storeBackup` took for `owner` (the version about to start), with no host running; without one,
425
- * touches nothing and says so. A write-ahead log the copy did not have is removed, so SQLite does not replay the dev
426
- * build's changes onto the restored database. What the dev build wrote to the store since is gone; nothing else in the
427
- * data folder is touched. The copy stays, so a second attempt finds it again
428
- */
429
- export function storeRestore(p, { dataDir = process.env.CC_DATA_DIR || join(homedir(), '.centralu') } = {}) {
430
- if (!WORD.test(String(p.owner))) throw new Refusal('params', 'store restore')
431
- const l = remoteLayout(dataDir)
432
- mkdirSync(l.root, { recursive: true })
433
- const unlock = lock(l)
434
- try {
435
- const dir = join(l.storeBackup, p.owner)
436
- let meta
437
- try {
438
- meta = JSON.parse(readFileSync(join(dir, 'backup.json'), 'utf8'))
439
- } catch {
440
- return { restored: false }
441
- }
442
- if (meta.owner !== p.owner || !Array.isArray(meta.files)) return { restored: false }
443
- try {
444
- for (const f of STORE_FILES) rmSync(join(dataDir, f), { force: true })
445
- for (const f of meta.files.filter((f) => STORE_FILES.includes(f))) cpSync(join(dir, f), join(dataDir, f))
446
- } catch (err) {
447
- throw new Refusal('store_restore', err.message)
448
- }
449
- return { restored: true, before: meta.before ?? null, at: meta.at ?? null }
450
- } finally {
451
- unlock()
452
- }
453
- }
454
-
455
291
  /** `a` at least `b`, both plain `x.y.z` (a prerelease or build suffix is ignored) */
456
292
  function atLeast(a, b) {
457
293
  const n = (v) => String(v).replace(/^v/, '').split(/[-+]/)[0].split('.').map((x) => Number(x) || 0)
@@ -500,8 +336,8 @@ function lastSaid(...texts) {
500
336
 
501
337
  /**
502
338
  * A dev build of the host from a git commit (`pnpm remote-dev`, #1036; docs/plans/remote-hub.md §10.11), placed beside
503
- * what is there as `<base>.local.N` with nothing switched: the hub's update steps stop, switch, start and check it
504
- * (update.ts `switchRemote`).
339
+ * what is there as `<base>.local.N` with nothing switched: the dev build's own `serve --switch` stops, switches, starts
340
+ * and checks it (update.ts `handOver`).
505
341
  *
506
342
  * Checked before anything is built: the working copy's HEAD is exactly `p.commit`, it has no changes unless
507
343
  * `p.allowDirty`, it speaks `p.protocol` (the protocol the hub links on), and this Node (the pinned one `current` runs
@@ -590,18 +426,7 @@ export function devBuild(p, { dataDir = process.env.CC_DATA_DIR || join(homedir(
590
426
  async function main() {
591
427
  try {
592
428
  const p = JSON.parse(Buffer.from(process.argv[2] ?? '', 'base64').toString('utf8'))
593
- const r =
594
- p.action === 'pointers'
595
- ? await setPointers(p)
596
- : p.action === 'prune'
597
- ? pruneNow()
598
- : p.action === 'dev'
599
- ? devBuild(p)
600
- : p.action === 'store-backup'
601
- ? storeBackup(p)
602
- : p.action === 'store-restore'
603
- ? storeRestore(p)
604
- : await install(p)
429
+ const r = p.action === 'dev' ? devBuild(p) : await install(p)
605
430
  process.stdout.write(`CENTRALU-INSTALL done ${JSON.stringify(r)}\n`)
606
431
  } catch (err) {
607
432
  const code = err instanceof Refusal ? err.code : 'error'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@centralu/host-linux-arm64",
3
- "version": "0.1.0-beta.46",
3
+ "version": "0.1.0-beta.48",
4
4
  "description": "Centralu's host without the window, for Linux on arm64: what a hub installs on a linked machine",
5
5
  "license": "MIT",
6
6
  "author": "ijun17",